From b557dae8b4d3bca03dfb450adef8142ec87d8fc3 Mon Sep 17 00:00:00 2001 From: Boris Bo Wang Date: Tue, 30 Jan 2024 17:19:47 +0800 Subject: [PATCH] feat: access token permission (#352) * feat: limit access token permission in scopes * fix: space order * fix: personal access token manange ui * fix: uploading attachment width and height * feat: permision guard set as global guard * fix: typecheck --- .../aggregation-open-api.controller.ts | 2 - .../src/features/auth/auth.service.ts | 8 + .../auth/decorators/token.decorator.ts | 5 + .../features/auth/guard/permission.guard.ts | 82 +++++-- .../src/features/auth/permission.module.ts | 10 +- .../src/features/auth/permission.service.ts | 21 +- .../auth/strategies/local.strategy.ts | 1 + .../src/features/base/base.controller.ts | 4 +- .../open-api/field-open-api.controller.ts | 15 +- .../open-api/record-open-api.controller.ts | 14 +- .../selection/selection.controller.ts | 4 +- .../src/features/space/space.controller.ts | 15 +- .../src/features/space/space.service.ts | 1 + .../open-api/table-open-api.controller.ts | 4 +- .../view/open-api/view-open-api.controller.ts | 4 +- .../setting/access-token/AccessTokenList.tsx | 16 +- .../setting/access-token/form/AccessList.tsx | 2 +- .../access-token/form/AccessTokenForm.tsx | 8 +- .../access-token/form/ExpirationSelect.tsx | 2 +- .../access-token/form/RefreshToken.tsx | 7 +- .../access-token/form/ScopesSelect.tsx | 26 ++- .../common-i18n/src/locales/en/common.json | 2 + packages/common-i18n/src/locales/en/sdk.json | 8 + .../common-i18n/src/locales/en/token.json | 2 +- .../common-i18n/src/locales/zh/common.json | 2 + packages/common-i18n/src/locales/zh/sdk.json | 8 + .../common-i18n/src/locales/zh/token.json | 2 +- .../upload-attachment/UploadAttachment.tsx | 5 +- .../use-permission-actions-static.spec.ts | 21 +- .../hooks/use-permission-actions-static.ts | 210 ++++++++++++------ 30 files changed, 318 insertions(+), 193 deletions(-) create mode 100644 apps/nestjs-backend/src/features/auth/decorators/token.decorator.ts diff --git a/apps/nestjs-backend/src/features/aggregation/open-api/aggregation-open-api.controller.ts b/apps/nestjs-backend/src/features/aggregation/open-api/aggregation-open-api.controller.ts index 3d4c54e0ba..6af5e4268b 100644 --- a/apps/nestjs-backend/src/features/aggregation/open-api/aggregation-open-api.controller.ts +++ b/apps/nestjs-backend/src/features/aggregation/open-api/aggregation-open-api.controller.ts @@ -11,12 +11,10 @@ import { } from '@teable-group/core'; import { ZodValidationPipe } from '../../../zod.validation.pipe'; import { Permissions } from '../../auth/decorators/permissions.decorator'; -import { PermissionGuard } from '../../auth/guard/permission.guard'; import { TqlPipe } from '../../record/open-api/tql.pipe'; import { AggregationOpenApiService } from './aggregation-open-api.service'; @Controller('api/table/:tableId/aggregation') -@UseGuards(PermissionGuard) export class AggregationOpenApiController { constructor(private readonly aggregationOpenApiService: AggregationOpenApiService) {} diff --git a/apps/nestjs-backend/src/features/auth/auth.service.ts b/apps/nestjs-backend/src/features/auth/auth.service.ts index 34d1fd4276..056d582548 100644 --- a/apps/nestjs-backend/src/features/auth/auth.service.ts +++ b/apps/nestjs-backend/src/features/auth/auth.service.ts @@ -59,6 +59,7 @@ export class AuthService { email, salt, password: hashPassword, + lastSignTime: new Date().toISOString(), }); } @@ -96,4 +97,11 @@ export class AuthService { // clear session await this.sessionStoreService.clearByUserId(userId); } + + async refreshLastSignTime(userId: string) { + await this.prismaService.user.update({ + where: { id: userId, deletedTime: null }, + data: { lastSignTime: new Date().toISOString() }, + }); + } } diff --git a/apps/nestjs-backend/src/features/auth/decorators/token.decorator.ts b/apps/nestjs-backend/src/features/auth/decorators/token.decorator.ts new file mode 100644 index 0000000000..e8c4d65b6c --- /dev/null +++ b/apps/nestjs-backend/src/features/auth/decorators/token.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common'; + +export const IS_TOKEN_ACCESS = 'isTokenAccess'; +// eslint-disable-next-line @typescript-eslint/naming-convention +export const TokenAccess = () => SetMetadata(IS_TOKEN_ACCESS, true); diff --git a/apps/nestjs-backend/src/features/auth/guard/permission.guard.ts b/apps/nestjs-backend/src/features/auth/guard/permission.guard.ts index c882ff46a8..5e49c62bf2 100644 --- a/apps/nestjs-backend/src/features/auth/guard/permission.guard.ts +++ b/apps/nestjs-backend/src/features/auth/guard/permission.guard.ts @@ -8,6 +8,7 @@ import { PERMISSIONS_KEY } from '../decorators/permissions.decorator'; import { IS_PUBLIC_KEY } from '../decorators/public.decorator'; import type { IResourceMeta } from '../decorators/resource_meta.decorator'; import { RESOURCE_META } from '../decorators/resource_meta.decorator'; +import { IS_TOKEN_ACCESS } from '../decorators/token.decorator'; import { PermissionService } from '../permission.service'; @Injectable() @@ -33,23 +34,20 @@ export class PermissionGuard { return req.params.baseId || req.params.spaceId || req.params.tableId; } - async canActivate(context: ExecutionContext) { - const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ - context.getHandler(), - context.getClass(), - ]); - - if (isPublic) { - return true; + /** + * Space creation permissions are more specific and only pertain to users, + * but tokens can be disallowed from being created. + */ + private async permissionCreateSpace() { + const accessTokenId = this.cls.get('accessTokenId'); + if (accessTokenId) { + const { scopes } = await this.permissionService.getAccessToken(accessTokenId); + return scopes.includes('space|create'); } - const permissions = this.reflector.getAllAndOverride( - PERMISSIONS_KEY, - [context.getHandler(), context.getClass()] - ); + return true; + } - if (!permissions?.length) { - return true; - } + private async resourcePermission(context: ExecutionContext, permissions: PermissionAction[]) { const resourceId = this.getResourceId(context); if (!resourceId) { throw new ForbiddenException('permission check ID does not exist'); @@ -85,4 +83,58 @@ export class PermissionGuard { this.cls.set('permissions', permissionsByCheck); return true; } + + /** + * permission step: + * 1. public decorator sign + * full public interface + * 2. token decorator sign + * The token can only access interfaces that are restricted by permissions or have a token access indicator. + * 3. permissions decorator sign + * Decorate what permissions are needed to operate the interface, + * if none then it means just logging in is sufficient + * 4. space create permission check + * The space create permission is special, it has nothing to do with resources, but only with users. + * 5. resource permission check + * Because the token is user-generated, the permissions will only be less than the current user, + * so first determine the current user permissions + * 5.1. by user for space + * 5.2. by access token if exists + */ + async canActivate(context: ExecutionContext) { + // public check + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + + if (isPublic) { + return true; + } + + const permissions = this.reflector.getAllAndOverride( + PERMISSIONS_KEY, + [context.getHandler(), context.getClass()] + ); + + const accessTokenId = this.cls.get('accessTokenId'); + if (accessTokenId && !permissions?.length) { + // Pre-checking of tokens + // The token can only access interfaces that are restricted by permissions or have a token access indicator. + return this.reflector.getAllAndOverride(IS_TOKEN_ACCESS, [ + context.getHandler(), + context.getClass(), + ]); + } + + if (!permissions?.length) { + return true; + } + // space create permission check + if (permissions?.includes('space|create')) { + return await this.permissionCreateSpace(); + } + // resource permission check + return await this.resourcePermission(context, permissions); + } } diff --git a/apps/nestjs-backend/src/features/auth/permission.module.ts b/apps/nestjs-backend/src/features/auth/permission.module.ts index 01343ad4f0..f332d4f704 100644 --- a/apps/nestjs-backend/src/features/auth/permission.module.ts +++ b/apps/nestjs-backend/src/features/auth/permission.module.ts @@ -1,10 +1,18 @@ import { Global, Module } from '@nestjs/common'; +import { APP_GUARD } from '@nestjs/core'; import { PermissionGuard } from './guard/permission.guard'; import { PermissionService } from './permission.service'; @Global() @Module({ - providers: [PermissionService, PermissionGuard], + providers: [ + PermissionService, + PermissionGuard, + { + provide: APP_GUARD, + useClass: PermissionGuard, + }, + ], exports: [PermissionService, PermissionGuard], }) export class PermissionModule {} diff --git a/apps/nestjs-backend/src/features/auth/permission.service.ts b/apps/nestjs-backend/src/features/auth/permission.service.ts index 11a2a89d86..36ecf58c6c 100644 --- a/apps/nestjs-backend/src/features/auth/permission.service.ts +++ b/apps/nestjs-backend/src/features/auth/permission.service.ts @@ -90,15 +90,24 @@ export class PermissionService { return await this.checkPermissionByBaseId(table.base.id, permissions); } + async getAccessToken(accessTokenId: string) { + const { scopes, spaceIds, baseIds } = await this.prismaService.accessToken.findFirstOrThrow({ + where: { id: accessTokenId }, + select: { scopes: true, spaceIds: true, baseIds: true }, + }); + return { + scopes: JSON.parse(scopes) as PermissionAction[], + spaceIds: spaceIds ? JSON.parse(spaceIds) : undefined, + baseIds: baseIds ? JSON.parse(baseIds) : undefined, + }; + } + async checkPermissionByAccessToken( resourceId: string, accessTokenId: string, permissions: PermissionAction[] ) { - const { scopes, spaceIds, baseIds } = await this.prismaService.accessToken.findFirstOrThrow({ - where: { id: accessTokenId }, - select: { scopes: true, spaceIds: true, baseIds: true }, - }); + const { scopes, spaceIds, baseIds } = await this.getAccessToken(accessTokenId); if (resourceId.startsWith(IdPrefix.Table)) { const table = await this.prismaService.tableMeta.findFirst({ @@ -123,11 +132,11 @@ export class PermissionService { throw new ForbiddenException(`not allowed to base ${resourceId}`); } - const accessTokenPermissions = JSON.parse(scopes) as PermissionAction[]; + const accessTokenPermissions = scopes; if (permissions.some((permission) => !accessTokenPermissions.includes(permission))) { throw new ForbiddenException(`not allowed to ${resourceId}`); } - return JSON.parse(scopes) as PermissionAction[]; + return scopes; } } diff --git a/apps/nestjs-backend/src/features/auth/strategies/local.strategy.ts b/apps/nestjs-backend/src/features/auth/strategies/local.strategy.ts index 805236f3b5..0ba796f6a1 100644 --- a/apps/nestjs-backend/src/features/auth/strategies/local.strategy.ts +++ b/apps/nestjs-backend/src/features/auth/strategies/local.strategy.ts @@ -18,6 +18,7 @@ export class LocalStrategy extends PassportStrategy(Strategy) { if (!user) { throw new BadRequestException('Incorrect password.'); } + await this.authService.refreshLastSignTime(user.id); return pickUserMe(user); } } diff --git a/apps/nestjs-backend/src/features/base/base.controller.ts b/apps/nestjs-backend/src/features/base/base.controller.ts index d63554b0ea..3136e003d8 100644 --- a/apps/nestjs-backend/src/features/base/base.controller.ts +++ b/apps/nestjs-backend/src/features/base/base.controller.ts @@ -1,5 +1,5 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'; import { createBaseRoSchema, ICreateBaseRo, @@ -18,13 +18,11 @@ import { Events } from '../../event-emitter/events'; import { ZodValidationPipe } from '../../zod.validation.pipe'; import { Permissions } from '../auth/decorators/permissions.decorator'; import { ResourceMeta } from '../auth/decorators/resource_meta.decorator'; -import { PermissionGuard } from '../auth/guard/permission.guard'; import { CollaboratorService } from '../collaborator/collaborator.service'; import { BaseService } from './base.service'; import { DbConnectionService } from './db-connection.service'; @Controller('api/base/') -@UseGuards(PermissionGuard) export class BaseController { constructor( private readonly baseService: BaseService, diff --git a/apps/nestjs-backend/src/features/field/open-api/field-open-api.controller.ts b/apps/nestjs-backend/src/features/field/open-api/field-open-api.controller.ts index a65195ff9c..744e6273f0 100644 --- a/apps/nestjs-backend/src/features/field/open-api/field-open-api.controller.ts +++ b/apps/nestjs-backend/src/features/field/open-api/field-open-api.controller.ts @@ -1,16 +1,5 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { - Body, - Controller, - Delete, - Get, - Param, - Patch, - Put, - Post, - Query, - UseGuards, -} from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Patch, Put, Post, Query } from '@nestjs/common'; import type { IFieldVo } from '@teable-group/core'; import { createFieldRoSchema, @@ -25,12 +14,10 @@ import { import type { IPlanFieldConvertVo, IPlanFieldVo } from '@teable-group/openapi'; import { ZodValidationPipe } from '../../../zod.validation.pipe'; import { Permissions } from '../../auth/decorators/permissions.decorator'; -import { PermissionGuard } from '../../auth/guard/permission.guard'; import { FieldService } from '../field.service'; import { FieldOpenApiService } from './field-open-api.service'; @Controller('api/table/:tableId/field') -@UseGuards(PermissionGuard) export class FieldOpenApiController { constructor( private readonly fieldService: FieldService, diff --git a/apps/nestjs-backend/src/features/record/open-api/record-open-api.controller.ts b/apps/nestjs-backend/src/features/record/open-api/record-open-api.controller.ts index 96e5b97f8c..c1a4fb455e 100644 --- a/apps/nestjs-backend/src/features/record/open-api/record-open-api.controller.ts +++ b/apps/nestjs-backend/src/features/record/open-api/record-open-api.controller.ts @@ -1,14 +1,4 @@ -import { - Body, - Controller, - Delete, - Get, - Param, - Patch, - Post, - Query, - UseGuards, -} from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; import type { ICreateRecordsVo, IRecord, IRecordsVo } from '@teable-group/core'; import { createRecordsRoSchema, @@ -23,13 +13,11 @@ import { import { deleteRecordsQuerySchema, IDeleteRecordsQuery } from '@teable-group/openapi'; import { ZodValidationPipe } from '../../../zod.validation.pipe'; import { Permissions } from '../../auth/decorators/permissions.decorator'; -import { PermissionGuard } from '../../auth/guard/permission.guard'; import { RecordService } from '../record.service'; import { RecordOpenApiService } from './record-open-api.service'; import { TqlPipe } from './tql.pipe'; @Controller('api/table/:tableId/record') -@UseGuards(PermissionGuard) export class RecordOpenApiController { constructor( private readonly recordService: RecordService, diff --git a/apps/nestjs-backend/src/features/selection/selection.controller.ts b/apps/nestjs-backend/src/features/selection/selection.controller.ts index d2aac0f91f..067e1eeb5d 100644 --- a/apps/nestjs-backend/src/features/selection/selection.controller.ts +++ b/apps/nestjs-backend/src/features/selection/selection.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Param, Patch, Query, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Param, Patch, Query } from '@nestjs/common'; import type { ICopyVo, IRangesToIdVo, IPasteVo } from '@teable-group/openapi'; import { IRangesToIdQuery, @@ -11,12 +11,10 @@ import { } from '@teable-group/openapi'; import { ZodValidationPipe } from '../../zod.validation.pipe'; import { Permissions } from '../auth/decorators/permissions.decorator'; -import { PermissionGuard } from '../auth/guard/permission.guard'; import { TqlPipe } from '../record/open-api/tql.pipe'; import { SelectionService } from './selection.service'; @Controller('api/table/:tableId/selection') -@UseGuards(PermissionGuard) export class SelectionController { constructor(private selectionService: SelectionService) {} diff --git a/apps/nestjs-backend/src/features/space/space.controller.ts b/apps/nestjs-backend/src/features/space/space.controller.ts index da6d73bc6d..100dd65946 100644 --- a/apps/nestjs-backend/src/features/space/space.controller.ts +++ b/apps/nestjs-backend/src/features/space/space.controller.ts @@ -1,15 +1,5 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { - Body, - Controller, - Param, - Patch, - Post, - Get, - Delete, - Query, - UseGuards, -} from '@nestjs/common'; +import { Body, Controller, Param, Patch, Post, Get, Delete, Query } from '@nestjs/common'; import type { ICreateSpaceVo, IUpdateSpaceVo, @@ -39,13 +29,11 @@ import { EmitControllerEvent } from '../../event-emitter/decorators/emit-control import { Events } from '../../event-emitter/events'; import { ZodValidationPipe } from '../../zod.validation.pipe'; import { Permissions } from '../auth/decorators/permissions.decorator'; -import { PermissionGuard } from '../auth/guard/permission.guard'; import { CollaboratorService } from '../collaborator/collaborator.service'; import { InvitationService } from '../invitation/invitation.service'; import { SpaceService } from './space.service'; @Controller('api/space/') -@UseGuards(PermissionGuard) export class SpaceController { constructor( private readonly spaceService: SpaceService, @@ -54,6 +42,7 @@ export class SpaceController { ) {} @Post() + @Permissions('space|create') @EmitControllerEvent(Events.SPACE_CREATE) async createSpace( @Body(new ZodValidationPipe(createSpaceRoSchema)) diff --git a/apps/nestjs-backend/src/features/space/space.service.ts b/apps/nestjs-backend/src/features/space/space.service.ts index a2a6e309a3..ee9f1352bc 100644 --- a/apps/nestjs-backend/src/features/space/space.service.ts +++ b/apps/nestjs-backend/src/features/space/space.service.ts @@ -87,6 +87,7 @@ export class SpaceService { const spaceList = await this.prismaService.space.findMany({ where: { id: { in: spaceIds } }, select: { id: true, name: true }, + orderBy: { createdTime: 'asc' }, }); const roleMap = keyBy(collaboratorSpaceList, 'spaceId'); return spaceList.map((space) => ({ diff --git a/apps/nestjs-backend/src/features/table/open-api/table-open-api.controller.ts b/apps/nestjs-backend/src/features/table/open-api/table-open-api.controller.ts index a067744797..efd9b2d42e 100644 --- a/apps/nestjs-backend/src/features/table/open-api/table-open-api.controller.ts +++ b/apps/nestjs-backend/src/features/table/open-api/table-open-api.controller.ts @@ -1,5 +1,5 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { Body, Controller, Delete, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common'; import type { ITableFullVo, ITableListVo, ITableVo } from '@teable-group/core'; import { getTableQuerySchema, @@ -25,13 +25,11 @@ import { } from '@teable-group/openapi'; import { ZodValidationPipe } from '../../../zod.validation.pipe'; import { Permissions } from '../../auth/decorators/permissions.decorator'; -import { PermissionGuard } from '../../auth/guard/permission.guard'; import { TableService } from '../table.service'; import { TableOpenApiService } from './table-open-api.service'; import { TablePipe } from './table.pipe'; @Controller('api/base/:baseId/table') -@UseGuards(PermissionGuard) export class TableController { constructor( private readonly tableService: TableService, diff --git a/apps/nestjs-backend/src/features/view/open-api/view-open-api.controller.ts b/apps/nestjs-backend/src/features/view/open-api/view-open-api.controller.ts index 84623894aa..c451f6bb4c 100644 --- a/apps/nestjs-backend/src/features/view/open-api/view-open-api.controller.ts +++ b/apps/nestjs-backend/src/features/view/open-api/view-open-api.controller.ts @@ -1,5 +1,5 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { Body, Controller, Delete, Get, Param, Patch, Post, Put, UseGuards } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common'; import type { IViewVo } from '@teable-group/core'; import { viewRoSchema, @@ -30,12 +30,10 @@ import { import type { EnableShareViewVo } from '@teable-group/openapi'; import { ZodValidationPipe } from '../../..//zod.validation.pipe'; import { Permissions } from '../../auth/decorators/permissions.decorator'; -import { PermissionGuard } from '../../auth/guard/permission.guard'; import { ViewService } from '../view.service'; import { ViewOpenApiService } from './view-open-api.service'; @Controller('api/table/:tableId/view') -@UseGuards(PermissionGuard) export class ViewOpenApiController { constructor( private readonly viewService: ViewService, diff --git a/apps/nextjs-app/src/features/app/blocks/setting/access-token/AccessTokenList.tsx b/apps/nextjs-app/src/features/app/blocks/setting/access-token/AccessTokenList.tsx index 6cae0f49ec..3e8ce82c8b 100644 --- a/apps/nextjs-app/src/features/app/blocks/setting/access-token/AccessTokenList.tsx +++ b/apps/nextjs-app/src/features/app/blocks/setting/access-token/AccessTokenList.tsx @@ -3,7 +3,7 @@ import type { AllActions } from '@teable-group/core'; import { Plus } from '@teable-group/icons'; import { deleteAccessToken, listAccessToken } from '@teable-group/openapi'; import { ReactQueryKeys } from '@teable-group/sdk/config'; -import { actionDescriptions } from '@teable-group/sdk/hooks'; +import { usePermissionActionsStatic } from '@teable-group/sdk/hooks'; import { ConfirmDialog } from '@teable-group/ui-lib/base'; import { Table, @@ -29,6 +29,7 @@ export const AccessTokenList = (props: { newToken?: string }) => { const newTokenRef = useRef(defaultNewToken); const newToken = newTokenRef.current; const router = useRouter(); + const { actionStaticMap } = usePermissionActionsStatic(); const { t } = useTranslation(personalAccessTokenConfig.i18nNamespaces); const [deleteId, setDeleteId] = useState(); const queryClient = useQueryClient(); @@ -111,10 +112,10 @@ export const AccessTokenList = (props: { newToken?: string }) => { ({ id, name, baseIds, spaceIds, scopes, expiredTime, lastUsedTime, createdTime }) => { const accessArr: string[] = []; if (baseIds?.length) { - accessArr.push(`${baseIds.length} base`); + accessArr.push(`${baseIds.length} ${t('common:noun.base')}`); } if (spaceIds?.length) { - accessArr.push(`${spaceIds.length} space`); + accessArr.push(`${spaceIds.length} ${t('common:noun.space')}`); } const scopesMoreLen = scopes.slice(2).length; return ( @@ -135,14 +136,7 @@ export const AccessTokenList = (props: { newToken?: string }) => { {scopes .slice(0, 2) - .map((action) => - t( - `sdk:permission.actionDescription.${ - actionDescriptions[action as AllActions] - // eslint-disable-next-line @typescript-eslint/no-explicit-any - }` as any - ) - ) + .map((action) => actionStaticMap[action as AllActions].description) .join('; ')} {scopesMoreLen ? ` ${t('token:moreScopes', { len: scopesMoreLen })}` : ''} diff --git a/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessList.tsx b/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessList.tsx index 8a7e4836c2..184e110fb1 100644 --- a/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessList.tsx +++ b/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessList.tsx @@ -68,7 +68,7 @@ export const AccessList = (props: IAccessListProps) => { }, [spaceIds, baseIds, spaceMap, baseMap]); return ( -
+
{allDisplaySpaceIds.map((spaceId) => { const space = spaceMap[spaceId]; const displaySpace = displaySpaceMap[spaceId]; diff --git a/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessTokenForm.tsx b/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessTokenForm.tsx index 17f8d98a07..9036161140 100644 --- a/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessTokenForm.tsx +++ b/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessTokenForm.tsx @@ -96,7 +96,7 @@ export const AccessTokenForm = (props: IAccessTokenForm) }; return ( -
+
{type === 'new' && ( <>

{t('token:new.title')}

@@ -105,7 +105,7 @@ export const AccessTokenForm = (props: IAccessTokenForm) )}
setName(e.target.value)} />
@@ -120,7 +120,7 @@ export const AccessTokenForm = (props: IAccessTokenForm) {type === 'new' && (
@@ -150,7 +150,7 @@ export const AccessTokenForm = (props: IAccessTokenForm) - diff --git a/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/ExpirationSelect.tsx b/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/ExpirationSelect.tsx index eca9f0cfc0..0f2283e108 100644 --- a/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/ExpirationSelect.tsx +++ b/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/ExpirationSelect.tsx @@ -64,7 +64,7 @@ export const ExpirationSelect = (props: IExpirationSelect) => {