mirror of https://github.com/nextcloud/server.git
enh(settings): Refactor frontend for session and app token management
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>pull/42430/head
parent
9b88869e0d
commit
fe60c1e1fd
@ -0,0 +1,114 @@
|
||||
<!--
|
||||
- @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
-
|
||||
- @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
- @author Ferdinand Thiessen <opensource@fthiessen.de>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- 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/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<form id="generate-app-token-section"
|
||||
class="row spacing"
|
||||
@submit.prevent="submit">
|
||||
<!-- Port to TextField component when available -->
|
||||
<NcTextField :value.sync="deviceName"
|
||||
type="text"
|
||||
:maxlength="120"
|
||||
:disabled="loading"
|
||||
class="app-name-text-field"
|
||||
:label="t('settings', 'App name')"
|
||||
:placeholder="t('settings', 'App name')" />
|
||||
<NcButton type="primary"
|
||||
:disabled="loading || deviceName.length === 0"
|
||||
native-type="submit">
|
||||
{{ t('settings', 'Create new app password') }}
|
||||
</NcButton>
|
||||
|
||||
<AuthTokenSetupDialog :token="newToken" @close="newToken = null" />
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { defineComponent } from 'vue'
|
||||
import { useAuthTokenStore, type ITokenResponse } from '../store/authtoken'
|
||||
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
|
||||
import AuthTokenSetupDialog from './AuthTokenSetupDialog.vue'
|
||||
import logger from '../logger'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AuthTokenSetup',
|
||||
components: {
|
||||
NcButton,
|
||||
NcTextField,
|
||||
AuthTokenSetupDialog,
|
||||
},
|
||||
setup() {
|
||||
const authTokenStore = useAuthTokenStore()
|
||||
return { authTokenStore }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deviceName: '',
|
||||
loading: false,
|
||||
newToken: null as ITokenResponse|null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
t,
|
||||
reset() {
|
||||
this.loading = false
|
||||
this.deviceName = ''
|
||||
this.newToken = null
|
||||
},
|
||||
async submit() {
|
||||
try {
|
||||
this.loading = true
|
||||
this.newToken = await this.authTokenStore.addToken(this.deviceName)
|
||||
} catch (error) {
|
||||
logger.error(error as Error)
|
||||
showError(t('settings', 'Error while creating device token'))
|
||||
this.reset()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-name-text-field {
|
||||
height: 44px !important;
|
||||
padding-left: 12px;
|
||||
margin-right: 12px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
padding-top: 16px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,220 @@
|
||||
<!--
|
||||
- @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/>.
|
||||
-
|
||||
-->
|
||||
<template>
|
||||
<NcDialog :open.sync="open"
|
||||
:name="t('settings', 'New app password')"
|
||||
content-classes="token-dialog">
|
||||
<p>
|
||||
{{ t('settings', 'Use the credentials below to configure your app or device. For security reasons this password will only be shown once.') }}
|
||||
</p>
|
||||
<div class="token-dialog__name">
|
||||
<NcTextField :label="t('settings', 'Username')" :value="loginName" readonly />
|
||||
<NcButton type="tertiary"
|
||||
:title="copyLoginNameLabel"
|
||||
:aria-label="copyLoginNameLabel"
|
||||
@click="copyLoginName">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="copyNameIcon" />
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
<div class="token-dialog__password">
|
||||
<NcTextField ref="appPassword"
|
||||
:label="t('settings', 'Password')"
|
||||
:value="appPassword"
|
||||
readonly />
|
||||
<NcButton type="tertiary"
|
||||
:title="copyPasswordLabel"
|
||||
:aria-label="copyPasswordLabel"
|
||||
@click="copyPassword">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="copyPasswordIcon" />
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
<div class="token-dialog__qrcode">
|
||||
<NcButton v-if="!showQRCode" @click="showQRCode = true">
|
||||
{{ t('settings', 'Show QR code for mobile apps') }}
|
||||
</NcButton>
|
||||
<QR v-else :value="qrUrl" />
|
||||
</div>
|
||||
</NcDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { ITokenResponse } from '../store/authtoken'
|
||||
|
||||
import { mdiCheck, mdiContentCopy } from '@mdi/js'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { getRootUrl } from '@nextcloud/router'
|
||||
import { defineComponent, type PropType } from 'vue'
|
||||
|
||||
import QR from '@chenfengyuan/vue-qrcode'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
|
||||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
|
||||
import logger from '../logger'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AuthTokenSetupDialog',
|
||||
components: {
|
||||
NcButton,
|
||||
NcDialog,
|
||||
NcIconSvgWrapper,
|
||||
NcTextField,
|
||||
QR,
|
||||
},
|
||||
props: {
|
||||
token: {
|
||||
type: Object as PropType<ITokenResponse|null>,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isNameCopied: false,
|
||||
isPasswordCopied: false,
|
||||
showQRCode: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
open: {
|
||||
get() {
|
||||
return this.token !== null
|
||||
},
|
||||
set(value: boolean) {
|
||||
if (!value) {
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
},
|
||||
copyPasswordIcon() {
|
||||
return this.isPasswordCopied ? mdiCheck : mdiContentCopy
|
||||
},
|
||||
copyNameIcon() {
|
||||
return this.isNameCopied ? mdiCheck : mdiContentCopy
|
||||
},
|
||||
appPassword() {
|
||||
return this.token?.token ?? ''
|
||||
},
|
||||
loginName() {
|
||||
return this.token?.loginName ?? ''
|
||||
},
|
||||
qrUrl() {
|
||||
const server = window.location.protocol + '//' + window.location.host + getRootUrl()
|
||||
return `nc://login/user:${this.loginName}&password:${this.appPassword}&server:${server}`
|
||||
},
|
||||
copyPasswordLabel() {
|
||||
if (this.isPasswordCopied) {
|
||||
return t('settings', 'App password copied!')
|
||||
}
|
||||
return t('settings', 'Copy app password')
|
||||
},
|
||||
copyLoginNameLabel() {
|
||||
if (this.isNameCopied) {
|
||||
return t('settings', 'Login name copied!')
|
||||
}
|
||||
return t('settings', 'Copy login name')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
token() {
|
||||
// reset showing the QR code on token change
|
||||
this.showQRCode = false
|
||||
},
|
||||
open() {
|
||||
if (this.open) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.appPassword!.select()
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
t,
|
||||
async copyPassword() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.appPassword)
|
||||
this.isPasswordCopied = true
|
||||
} catch (e) {
|
||||
this.isPasswordCopied = false
|
||||
logger.error(e as Error)
|
||||
showError(t('settings', 'Could not copy app password. Please copy it manually.'))
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.isPasswordCopied = false
|
||||
}, 4000)
|
||||
}
|
||||
},
|
||||
async copyLoginName() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.loginName)
|
||||
this.isNameCopied = true
|
||||
} catch (e) {
|
||||
this.isNameCopied = false
|
||||
logger.error(e as Error)
|
||||
showError(t('settings', 'Could not copy login name. Please copy it manually.'))
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.isNameCopied = false
|
||||
}, 4000)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.token-dialog) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
padding-inline: 22px;
|
||||
padding-block-end: 20px;
|
||||
|
||||
> * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.token-dialog {
|
||||
&__name, &__password {
|
||||
align-items: end;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
:deep(input) {
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
|
||||
&__qrcode {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,239 +0,0 @@
|
||||
<!--
|
||||
- @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
-
|
||||
- @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- 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/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div v-if="!adding" id="generate-app-token-section" class="row spacing">
|
||||
<!-- Port to TextField component when available -->
|
||||
<NcTextField :value.sync="deviceName"
|
||||
type="text"
|
||||
:maxlength="120"
|
||||
:disabled="loading"
|
||||
class="app-name-text-field"
|
||||
:label="t('settings', 'App name')"
|
||||
:placeholder="t('settings', 'App name')"
|
||||
@keydown.enter="submit" />
|
||||
<NcButton :disabled="loading || deviceName.length === 0"
|
||||
type="primary"
|
||||
@click="submit">
|
||||
{{ t('settings', 'Create new app password') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
<div v-else class="spacing">
|
||||
{{ t('settings', 'Use the credentials below to configure your app or device.') }}
|
||||
{{ t('settings', 'For security reasons this password will only be shown once.') }}
|
||||
<div class="app-password-row">
|
||||
<label for="app-username" class="app-password-label">{{ t('settings', 'Username') }}</label>
|
||||
<input id="app-username"
|
||||
:value="loginName"
|
||||
type="text"
|
||||
class="monospaced"
|
||||
readonly="readonly"
|
||||
@focus="selectInput">
|
||||
</div>
|
||||
<div class="app-password-row">
|
||||
<label for="app-password" class="app-password-label">{{ t('settings', 'Password') }}</label>
|
||||
<input id="app-password"
|
||||
ref="appPassword"
|
||||
:value="appPassword"
|
||||
type="text"
|
||||
class="monospaced"
|
||||
readonly="readonly"
|
||||
@focus="selectInput">
|
||||
<NcButton type="tertiary"
|
||||
:title="copyTooltipOptions"
|
||||
:aria-label="copyTooltipOptions"
|
||||
@click="copyPassword">
|
||||
<template #icon>
|
||||
<Check v-if="copied" :size="20" />
|
||||
<ContentCopy v-else :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
<NcButton @click="reset">
|
||||
{{ t('settings', 'Done') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
<div class="app-password-row">
|
||||
<span class="app-password-label" />
|
||||
<NcButton v-if="!showQR"
|
||||
@click="showQR = true">
|
||||
{{ t('settings', 'Show QR code for mobile apps') }}
|
||||
</NcButton>
|
||||
<QR v-else
|
||||
:value="qrUrl" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QR from '@chenfengyuan/vue-qrcode'
|
||||
import { confirmPassword } from '@nextcloud/password-confirmation'
|
||||
import '@nextcloud/password-confirmation/dist/style.css'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { getRootUrl } from '@nextcloud/router'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
|
||||
import Check from 'vue-material-design-icons/Check.vue'
|
||||
import ContentCopy from 'vue-material-design-icons/ContentCopy.vue'
|
||||
|
||||
export default {
|
||||
name: 'AuthTokenSetupDialogue',
|
||||
components: {
|
||||
Check,
|
||||
ContentCopy,
|
||||
NcButton,
|
||||
QR,
|
||||
NcTextField,
|
||||
},
|
||||
props: {
|
||||
add: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
adding: false,
|
||||
loading: false,
|
||||
deviceName: '',
|
||||
appPassword: '',
|
||||
loginName: '',
|
||||
copied: false,
|
||||
showQR: false,
|
||||
qrUrl: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
copyTooltipOptions() {
|
||||
if (this.copied) {
|
||||
return t('settings', 'Copied!')
|
||||
}
|
||||
return t('settings', 'Copy')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selectInput(e) {
|
||||
e.currentTarget.select()
|
||||
},
|
||||
submit() {
|
||||
confirmPassword()
|
||||
.then(() => {
|
||||
this.loading = true
|
||||
return this.add(this.deviceName)
|
||||
})
|
||||
.then(token => {
|
||||
this.adding = true
|
||||
this.loginName = token.loginName
|
||||
this.appPassword = token.token
|
||||
|
||||
const server = window.location.protocol + '//' + window.location.host + getRootUrl()
|
||||
this.qrUrl = `nc://login/user:${token.loginName}&password:${token.token}&server:${server}`
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.appPassword.select()
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('could not create a new app password', err)
|
||||
OC.Notification.showTemporary(t('settings', 'Error while creating device token'))
|
||||
|
||||
this.reset()
|
||||
})
|
||||
},
|
||||
async copyPassword() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.appPassword)
|
||||
this.copied = true
|
||||
} catch (e) {
|
||||
this.copied = false
|
||||
console.error(e)
|
||||
showError(t('settings', 'Could not copy app password. Please copy it manually.'))
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.copied = false
|
||||
}, 4000)
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.adding = false
|
||||
this.loading = false
|
||||
this.showQR = false
|
||||
this.qrUrl = ''
|
||||
this.deviceName = ''
|
||||
this.appPassword = ''
|
||||
this.loginName = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-password-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: calc(var(--default-grid-baseline) * 2);
|
||||
|
||||
.icon {
|
||||
background-size: 16px 16px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
margin-left: 5px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.app-password-label {
|
||||
display: table-cell;
|
||||
margin-right: 1em;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.app-name-text-field {
|
||||
height: 44px !important;
|
||||
padding-left: 12px;
|
||||
margin-right: 12px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.monospaced {
|
||||
width: 245px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.button-vue{
|
||||
display:inline-block;
|
||||
margin: 3px 3px 3px 3px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
padding-top: 16px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,214 @@
|
||||
/**
|
||||
* @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
|
||||
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
|
||||
},
|
||||
},
|
||||
|
||||
})
|
Loading…
Reference in New Issue