show grantee and rank info in tokens list

This commit is contained in:
Hazelnoot 2025-06-21 10:04:41 -04:00
parent fe53c16b23
commit 23e69eccbb
5 changed files with 86 additions and 18 deletions

8
locales/index.d.ts vendored
View file

@ -13512,13 +13512,17 @@ export interface Locale extends ILocale {
*/
"permissions": string;
/**
* Override rank
* Limit rank
*/
"overrideRank": string;
/**
* Overrides the user rank (admin, moderator, or user) for apps using this token.
* Limits the user rank (admin, moderator, or user) for apps using this token.
*/
"overrideRankDescription": string;
/**
* Rank
*/
"rank": string;
"_ranks": {
/**
* Admin

View file

@ -5,9 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AccessTokensRepository } from '@/models/_.js';
import type { AccessTokensRepository, SharedAccessTokensRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { IdService } from '@/core/IdService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
export const meta = {
requireCredential: true,
@ -46,6 +47,19 @@ export const meta = {
type: 'string',
},
},
grantees: {
type: 'array',
optional: false,
items: {
ref: 'UserLite',
},
},
rank: {
type: 'string',
optional: false,
nullable: true,
enum: ['admin', 'mod', 'user'],
},
},
},
},
@ -71,6 +85,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.accessTokensRepository)
private accessTokensRepository: AccessTokensRepository,
@Inject(DI.sharedAccessTokensRepository)
private readonly sharedAccessTokenRepository: SharedAccessTokensRepository,
private readonly userEntityService: UserEntityService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
@ -88,13 +106,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const tokens = await query.getMany();
return await Promise.all(tokens.map(token => ({
id: token.id,
name: token.name ?? token.app?.name,
createdAt: this.idService.parse(token.id).date.toISOString(),
lastUsedAt: token.lastUsedAt?.toISOString(),
permission: token.app ? token.app.permission : token.permission,
})));
return await Promise.all(tokens.map(async token => {
// TODO inline this table into a column w/ GIN index
const sharedTokens = await this.sharedAccessTokenRepository.find({
where: { accessTokenId: token.id },
relations: { grantee: true },
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const grantees = await this.userEntityService.packMany(sharedTokens.map(t => t.grantee!), me);
return {
id: token.id,
name: token.name ?? token.app?.name,
createdAt: this.idService.parse(token.id).date.toISOString(),
lastUsedAt: token.lastUsedAt?.toISOString(),
permission: token.app ? token.app.permission : token.permission,
rank: token.rank,
grantees,
};
}));
});
}
}

View file

@ -32,14 +32,31 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #key>{{ i18n.ts.lastUsedDate }}</template>
<template #value><MkTime :time="token.lastUsedAt" :mode="'detail'"/></template>
</MkKeyValue>
<MkKeyValue v-if="token.rank" oneline>
<template #key>{{ i18n.ts.rank }}</template>
<template #value>{{ i18n.ts._ranks[token.rank] ?? token.rank }}</template>
</MkKeyValue>
</div>
<MkFolder>
<MkFolder v-if="standardPerms(token.permissions).length > 0">
<template #label>{{ i18n.ts.permission }}</template>
<template #suffix>{{ Object.keys(token.permission).length === 0 ? i18n.ts.none : Object.keys(token.permission).length }}</template>
<template #suffix>{{ standardPerms(token.permissions).length }}</template>
<ul>
<li v-for="p in token.permission" :key="p">{{ i18n.ts._permissions[p] }}</li>
<li v-for="p of standardPerms(token.permissions)" :key="p">{{ i18n.ts._permissions[p] }}</li>
</ul>
</MkFolder>
<MkFolder v-if="adminPerms(token.permissions).length > 0">
<template #label>{{ i18n.ts.adminPermission }}</template>
<template #suffix>{{ adminPerms(token.permissions).length }}</template>
<ul>
<li v-for="p of adminPerms(token.permissions)" :key="p">{{ i18n.ts._permissions[p] }}</li>
</ul>
</MkFolder>
<MkFolder v-if="token.grantees.length > 0">
<template #label>{{ i18n.ts.sharedAccess }}</template>
<template #suffix>{{ token.grantees.length }}</template>
<MkUserCardMini v-for="grantee of token.grantees" :key="grantee.id" :user="grantee" :withChart="false"/>
</MkFolder>
</div>
</MkFolder>
</div>
@ -50,7 +67,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import FormPagination from '@/components/MkPagination.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
@ -58,6 +74,7 @@ import { definePage } from '@/page.js';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
const list = ref<InstanceType<typeof FormPagination>>();
@ -76,6 +93,18 @@ function revoke(token) {
});
}
function isAdmin(perm: string): boolean {
return perm.startsWith('read:admin') || perm.startsWith('write:admin');
}
function standardPerms(perms: string[]): string[] {
return perms.filter(perm => !isAdmin(perm));
}
function adminPerms(perms: string[]): string[] {
return perms.filter(perm => isAdmin(perm));
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);

View file

@ -22670,7 +22670,7 @@ export type operations = {
/** @description OK (with results) */
200: {
content: {
'application/json': {
'application/json': ({
/** Format: misskey:id */
id: string;
name?: string;
@ -22679,7 +22679,10 @@ export type operations = {
/** Format: date-time */
lastUsedAt?: string;
permission: string[];
}[];
grantees: components['schemas']['UserLite'][];
/** @enum {string|null} */
rank: 'admin' | 'mod' | 'user';
})[];
};
};
/** @description Client error */

View file

@ -692,8 +692,9 @@ noSharedAccess: "You have not been granted shared access to any accounts"
expand: "Expand"
collapse: "Collapse"
permissions: "Permissions"
overrideRank: "Override rank"
overrideRankDescription: "Overrides the user rank (admin, moderator, or user) for apps using this token."
overrideRank: "Limit rank"
overrideRankDescription: "Limits the user rank (admin, moderator, or user) for apps using this token."
rank: "Rank"
_ranks:
admin: "Admin"
mod: "Moderator"