feat(api tokens): add basic api token overview

This commit is contained in:
kolaente 2023-09-01 11:15:48 +02:00
parent 7b57b10804
commit a20eef2453
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
7 changed files with 148 additions and 0 deletions

View File

@ -139,6 +139,17 @@
"system": "System",
"dark": "Dark"
}
},
"apiTokens": {
"title": "API Tokens",
"general": "API tokens allow you to use Vikunja's api without user credentials.",
"apiDocs": "Check out the api docs",
"createToken": "Create a token",
"attributes": {
"title": "Title",
"expiresAt": "Expires at",
"permissions": "Permissions"
}
}
},
"deletion": {

View File

@ -0,0 +1,13 @@
import type {IAbstract} from '@/modelTypes/IAbstract'
export interface IApiPermission {
[key: string]: string[]
}
export interface IApiToken extends IAbstract {
id: number
token: string
permissions: IApiPermission
expiresAt: Date
created: Date
}

View File

@ -0,0 +1,20 @@
import AbstractModel from '@/models/abstractModel'
import type {IApiToken} from '@/modelTypes/IApiToken'
export default class ApiTokenModel extends AbstractModel<IApiToken> {
id = 0
token = ''
permissions = null
expiresAt: Date = null
created: Date = null
constructor(data: Partial<IApiToken>) {
super()
this.assignData(data)
this.expiresAt = new Date(this.expiresAt)
this.created = new Date(this.created)
this.updated = new Date(this.updated)
}
}

View File

@ -65,6 +65,7 @@ const UserSettingsEmailUpdateComponent = () => import('@/views/user/settings/Ema
const UserSettingsGeneralComponent = () => import('@/views/user/settings/General.vue')
const UserSettingsPasswordUpdateComponent = () => import('@/views/user/settings/PasswordUpdate.vue')
const UserSettingsTOTPComponent = () => import('@/views/user/settings/TOTP.vue')
const UserSettingsApiTokensComponent = () => import('@/views/user/settings/ApiTokens.vue')
// Project Handling
const NewProjectComponent = () => import('@/views/project/NewProject.vue')
@ -183,6 +184,11 @@ const router = createRouter({
name: 'user.settings.totp',
component: UserSettingsTOTPComponent,
},
{
path: '/user/settings/api-tokens',
name: 'user.settings.apiTokens',
component: UserSettingsApiTokensComponent,
},
],
},
{

25
src/services/apiToken.ts Normal file
View File

@ -0,0 +1,25 @@
import AbstractService from '@/services/abstractService'
import type {IApiToken} from '@/modelTypes/IApiToken'
import ApiTokenModel from '@/models/apiTokenModel'
export default class ApiTokenService extends AbstractService<IApiToken> {
constructor() {
super({
create: '/tokens',
getAll: '/tokens',
delete: '/tokens/{id}',
})
}
processModel(model: IApiToken) {
return {
...model,
expiresAt: new Date(model.expiresAt).toISOString(),
created: new Date(model.created).toISOString(),
}
}
modelFactory(data: Partial<IApiToken>) {
return new ApiTokenModel(data)
}
}

View File

@ -75,6 +75,10 @@ const navigationItems = computed(() => {
routeName: 'user.settings.caldav',
condition: caldavEnabled.value,
},
{
title: t('user.settings.apiTokens.title'),
routeName: 'user.settings.apiTokens',
},
{
title: t('user.deletion.title'),
routeName: 'user.settings.deletion',

View File

@ -0,0 +1,69 @@
<script setup lang="ts">
import ApiTokenService from '@/services/apiToken'
import {computed, onMounted, ref} from 'vue'
import { formatDateShort } from '@/helpers/time/formatDate'
import XButton from '@/components/input/button.vue'
import BaseButton from '@/components/base/BaseButton.vue'
const service = new ApiTokenService()
const tokens = ref([])
const apiDocsUrl = window.API_URL + '/docs'
onMounted(async () => {
tokens.value = await service.getAll()
})
function deleteToken() {
}
function createToken() {
}
</script>
<template>
<card :title="$t('user.settings.apiTokens.title')">
<p>
{{ $t('user.settings.apiTokens.general') }}
<BaseButton :href="apiDocsUrl">{{ $t('user.settings.apiTokens.apiDocs') }}</BaseButton>.
</p>
<table class="table" v-if="tokens.length > 0">
<tr>
<th>{{ $t('misc.id') }}</th>
<th>{{ $t('user.settings.apiTokens.attributes.title') }}</th>
<th>{{ $t('user.settings.apiTokens.attributes.permissions') }}</th>
<th>{{ $t('user.settings.apiTokens.attributes.expiresAt') }}</th>
<th>{{ $t('misc.created') }}</th>
<th class="has-text-right">{{ $t('misc.actions') }}</th>
</tr>
<tr v-for="tk in tokens" :key="tk.id">
<td>{{ tk.id }}</td>
<td>{{ tk.title }}</td>
<td>
<template v-for="(v, p) in tk.permissions">
<strong>{{ p }}:</strong>
{{ v.join(', ') }}
<br/>
</template>
</td>
<td>{{ formatDateShort(tk.expiresAt) }}</td>
<td>{{ formatDateShort(tk.created) }}</td>
<td class="has-text-right">
<x-button variant="secondary" @click="deleteToken(tk)">
{{ $t('misc.delete') }}
</x-button>
</td>
</tr>
</table>
<x-button icon="plus" class="mb-4" @click="createToken" :loading="service.loading">
{{ $t('user.settings.apiTokens.createToken') }}
</x-button>
</card>
</template>
<style scoped lang="scss">
</style>