216 lines
5.3 KiB
TypeScript
216 lines
5.3 KiB
TypeScript
/**
|
|
* @copyright 2023 Ferdinand Thiessen <opensource@fthiessen.de>
|
|
*
|
|
* @author Ferdinand Thiessen <opensource@fthiessen.de>
|
|
*
|
|
* @license AGPL-3.0-or-later
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
import { showError } from '@nextcloud/dialogs'
|
|
import { loadState } from '@nextcloud/initial-state'
|
|
import { translate as t } from '@nextcloud/l10n'
|
|
import { confirmPassword } from '@nextcloud/password-confirmation'
|
|
import { generateUrl } from '@nextcloud/router'
|
|
import { defineStore } from 'pinia'
|
|
|
|
import axios from '@nextcloud/axios'
|
|
import logger from '../logger'
|
|
|
|
const BASE_URL = generateUrl('/settings/personal/authtokens')
|
|
|
|
const confirm = () => {
|
|
return new Promise(resolve => {
|
|
window.OC.dialogs.confirm(
|
|
t('settings', 'Do you really want to wipe your data from this device?'),
|
|
t('settings', 'Confirm wipe'),
|
|
resolve,
|
|
true,
|
|
)
|
|
})
|
|
}
|
|
|
|
export enum TokenType {
|
|
TEMPORARY_TOKEN = 0,
|
|
PERMANENT_TOKEN = 1,
|
|
WIPING_TOKEN = 2,
|
|
}
|
|
|
|
export interface IToken {
|
|
id: number
|
|
canDelete: boolean
|
|
canRename: boolean
|
|
current?: true
|
|
/**
|
|
* Last activity as UNIX timestamp (in seconds)
|
|
*/
|
|
lastActivity: number
|
|
name: string
|
|
type: TokenType
|
|
scope: Record<string, boolean>
|
|
}
|
|
|
|
export interface ITokenResponse {
|
|
/**
|
|
* The device token created
|
|
*/
|
|
deviceToken: IToken
|
|
/**
|
|
* User who is assigned with this token
|
|
*/
|
|
loginName: string
|
|
/**
|
|
* The token for authentication
|
|
*/
|
|
token: string
|
|
}
|
|
|
|
export const useAuthTokenStore = defineStore('auth-token', {
|
|
state() {
|
|
return {
|
|
tokens: loadState<IToken[]>('settings', 'app_tokens', []),
|
|
}
|
|
},
|
|
actions: {
|
|
/**
|
|
* Update a token on server
|
|
* @param token Token to update
|
|
*/
|
|
async updateToken(token: IToken) {
|
|
const { data } = await axios.put(`${BASE_URL}/${token.id}`, token)
|
|
return data
|
|
},
|
|
|
|
/**
|
|
* Add a new token
|
|
* @param name The token name
|
|
*/
|
|
async addToken(name: string) {
|
|
logger.debug('Creating a new app token')
|
|
|
|
try {
|
|
await confirmPassword()
|
|
|
|
const { data } = await axios.post<ITokenResponse>(BASE_URL, { name })
|
|
this.tokens.push(data.deviceToken)
|
|
logger.debug('App token created')
|
|
return data
|
|
} catch (error) {
|
|
return null
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Delete a given app token
|
|
* @param token Token to delete
|
|
*/
|
|
async deleteToken(token: IToken) {
|
|
logger.debug('Deleting app token', { token })
|
|
|
|
this.tokens = this.tokens.filter(({ id }) => id !== token.id)
|
|
|
|
try {
|
|
await axios.delete(`${BASE_URL}/${token.id}`)
|
|
logger.debug('App token deleted')
|
|
return true
|
|
} catch (error) {
|
|
logger.error('Could not delete app token', { error })
|
|
showError(t('settings', 'Could not delete the app token'))
|
|
// Restore
|
|
this.tokens.push(token)
|
|
}
|
|
return false
|
|
},
|
|
|
|
/**
|
|
* Wipe a token and the connected device
|
|
* @param token Token to wipe
|
|
*/
|
|
async wipeToken(token: IToken) {
|
|
logger.debug('Wiping app token', { token })
|
|
|
|
try {
|
|
await confirmPassword()
|
|
|
|
if (!(await confirm())) {
|
|
logger.debug('Wipe aborted by user')
|
|
return
|
|
}
|
|
|
|
await axios.post(`${BASE_URL}/wipe/${token.id}`)
|
|
logger.debug('App token marked for wipe', { token })
|
|
|
|
token.type = TokenType.WIPING_TOKEN
|
|
token.canRename = false // wipe tokens can not be renamed
|
|
return true
|
|
} catch (error) {
|
|
logger.error('Could not wipe app token', { error })
|
|
showError(t('settings', 'Error while wiping the device with the token'))
|
|
}
|
|
return false
|
|
},
|
|
|
|
/**
|
|
* Rename an existing token
|
|
* @param token The token to rename
|
|
* @param newName The new name to set
|
|
*/
|
|
async renameToken(token: IToken, newName: string) {
|
|
logger.debug(`renaming app token ${token.id} from ${token.name} to '${newName}'`)
|
|
|
|
const oldName = token.name
|
|
token.name = newName
|
|
|
|
try {
|
|
await this.updateToken(token)
|
|
logger.debug('App token name updated')
|
|
return true
|
|
} catch (error) {
|
|
logger.error('Could not update app token name', { error })
|
|
showError(t('settings', 'Error while updating device token name'))
|
|
// Restore
|
|
token.name = oldName
|
|
}
|
|
return false
|
|
},
|
|
|
|
/**
|
|
* Set scope of the token
|
|
* @param token Token to set scope
|
|
* @param scope scope to set
|
|
* @param value value to set
|
|
*/
|
|
async setTokenScope(token: IToken, scope: string, value: boolean) {
|
|
logger.debug('Updating app token scope', { token, scope, value })
|
|
|
|
const oldVal = token.scope[scope]
|
|
token.scope[scope] = value
|
|
|
|
try {
|
|
await this.updateToken(token)
|
|
logger.debug('app token scope updated')
|
|
return true
|
|
} catch (error) {
|
|
logger.error('could not update app token scope', { error })
|
|
showError(t('settings', 'Error while updating device token scope'))
|
|
// Restore
|
|
token.scope[scope] = oldVal
|
|
}
|
|
return false
|
|
},
|
|
},
|
|
|
|
})
|