diff --git a/src/i18n/lang/en.json b/src/i18n/lang/en.json index be6bf4ed60..538a62a753 100644 --- a/src/i18n/lang/en.json +++ b/src/i18n/lang/en.json @@ -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": { diff --git a/src/modelTypes/IApiToken.ts b/src/modelTypes/IApiToken.ts new file mode 100644 index 0000000000..842e242ade --- /dev/null +++ b/src/modelTypes/IApiToken.ts @@ -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 +} \ No newline at end of file diff --git a/src/models/apiTokenModel.ts b/src/models/apiTokenModel.ts new file mode 100644 index 0000000000..dc3d69f17b --- /dev/null +++ b/src/models/apiTokenModel.ts @@ -0,0 +1,20 @@ +import AbstractModel from '@/models/abstractModel' +import type {IApiToken} from '@/modelTypes/IApiToken' + +export default class ApiTokenModel extends AbstractModel { + id = 0 + token = '' + permissions = null + expiresAt: Date = null + created: Date = null + + constructor(data: Partial) { + super() + + this.assignData(data) + + this.expiresAt = new Date(this.expiresAt) + this.created = new Date(this.created) + this.updated = new Date(this.updated) + } +} \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 3aaa786e96..d00b6d4c4b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -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, + }, ], }, { diff --git a/src/services/apiToken.ts b/src/services/apiToken.ts new file mode 100644 index 0000000000..5c29370e3a --- /dev/null +++ b/src/services/apiToken.ts @@ -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 { + 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) { + return new ApiTokenModel(data) + } +} \ No newline at end of file diff --git a/src/views/user/Settings.vue b/src/views/user/Settings.vue index 0af37c141c..4403e36501 100644 --- a/src/views/user/Settings.vue +++ b/src/views/user/Settings.vue @@ -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', diff --git a/src/views/user/settings/ApiTokens.vue b/src/views/user/settings/ApiTokens.vue new file mode 100644 index 0000000000..2ae763f0c5 --- /dev/null +++ b/src/views/user/settings/ApiTokens.vue @@ -0,0 +1,69 @@ + + + + + \ No newline at end of file