diff --git a/packages/nocodb-nest/src/app.module.ts b/packages/nocodb-nest/src/app.module.ts index 0cf99c33e8..e58642c9e1 100644 --- a/packages/nocodb-nest/src/app.module.ts +++ b/packages/nocodb-nest/src/app.module.ts @@ -31,9 +31,10 @@ import { HookFiltersModule } from './modules/hook-filters/hook-filters.module'; import { ApiTokensModule } from './modules/api-tokens/api-tokens.module'; import { AttachmentsModule } from './modules/attachments/attachments.module'; import { OrgLcenseModule } from './modules/org-lcense/org-lcense.module'; +import { OrgTokensModule } from './modules/org-tokens/org-tokens.module'; @Module({ - imports: [AuthModule, UsersModule, UtilsModule, ProjectsModule, TablesModule, ViewsModule, FiltersModule, SortsModule, ColumnsModule, ViewColumnsModule, BasesModule, HooksModule, SharedBasesModule, FormsModule, GridsModule, KanbansModule, GalleriesModule, FormColumnsModule, GridColumnsModule, MapsModule, ProjectUsersModule, ModelVisibilitiesModule, HookFiltersModule, ApiTokensModule, AttachmentsModule, OrgLcenseModule], + imports: [AuthModule, UsersModule, UtilsModule, ProjectsModule, TablesModule, ViewsModule, FiltersModule, SortsModule, ColumnsModule, ViewColumnsModule, BasesModule, HooksModule, SharedBasesModule, FormsModule, GridsModule, KanbansModule, GalleriesModule, FormColumnsModule, GridColumnsModule, MapsModule, ProjectUsersModule, ModelVisibilitiesModule, HookFiltersModule, ApiTokensModule, AttachmentsModule, OrgLcenseModule, OrgTokensModule], controllers: [], providers: [Connection, MetaService, JwtStrategy, ExtractProjectIdMiddleware], exports: [Connection, MetaService], diff --git a/packages/nocodb-nest/src/modules/org-tokens/ee/org-tokens/org-tokens-ee.service.spec.ts b/packages/nocodb-nest/src/modules/org-tokens/ee/org-tokens/org-tokens-ee.service.spec.ts new file mode 100644 index 0000000000..1d7cb8c1d1 --- /dev/null +++ b/packages/nocodb-nest/src/modules/org-tokens/ee/org-tokens/org-tokens-ee.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { OrgTokensEeService } from './org-tokens.service'; + +describe('OrgTokensService', () => { + let service: OrgTokensEeService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [OrgTokensEeService], + }).compile(); + + service = module.get(OrgTokensEeService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/packages/nocodb-nest/src/modules/org-tokens/ee/org-tokens/org-tokens-ee.service.ts b/packages/nocodb-nest/src/modules/org-tokens/ee/org-tokens/org-tokens-ee.service.ts new file mode 100644 index 0000000000..06f4a340c2 --- /dev/null +++ b/packages/nocodb-nest/src/modules/org-tokens/ee/org-tokens/org-tokens-ee.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { OrgUserRoles, UserType } from 'nocodb-sdk'; +import { PagedResponseImpl } from '../../../../helpers/PagedResponse'; +import { ApiToken } from '../../../../models'; + +@Injectable() +export class OrgTokensEeService { + async apiTokenListEE(param: { user: UserType; query: any }) { + let fk_user_id = param.user.id; + + // if super admin get all tokens + if (param.user.roles.includes(OrgUserRoles.SUPER_ADMIN)) { + fk_user_id = undefined; + } + + return new PagedResponseImpl( + await ApiToken.listWithCreatedBy({ ...param.query, fk_user_id }), + { + ...(param.query || {}), + count: await ApiToken.count({}), + }, + ); + } +} diff --git a/packages/nocodb-nest/src/modules/org-tokens/org-tokens.controller.spec.ts b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.controller.spec.ts new file mode 100644 index 0000000000..264f3d570e --- /dev/null +++ b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { OrgTokensController } from './org-tokens.controller'; +import { OrgTokensService } from './org-tokens.service'; + +describe('OrgTokensController', () => { + let controller: OrgTokensController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [OrgTokensController], + providers: [OrgTokensService], + }).compile(); + + controller = module.get(OrgTokensController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/packages/nocodb-nest/src/modules/org-tokens/org-tokens.controller.ts b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.controller.ts new file mode 100644 index 0000000000..b782e83ad8 --- /dev/null +++ b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.controller.ts @@ -0,0 +1,66 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Request, + UseGuards, +} from '@nestjs/common'; +import { ApiTokenReqType } from 'nocodb-sdk'; +import { getConditionalHandler } from '../../helpers/getHandler'; +import { + Acl, + ExtractProjectIdMiddleware, +} from '../../middlewares/extract-project-id/extract-project-id.middleware'; +import { OrgTokensEeService } from './ee/org-tokens/org-tokens-ee.service'; +import { OrgTokensService } from './org-tokens.service'; +import { AuthGuard } from '@nestjs/passport'; + +@UseGuards(ExtractProjectIdMiddleware, AuthGuard('jwt')) +@Controller('org-tokens') +export class OrgTokensController { + constructor( + private readonly orgTokensService: OrgTokensService, + private readonly orgTokensEeService: OrgTokensEeService, + ) {} + + @Get('/api/v1/tokens') + @Acl('apiTokenList', { + blockApiTokenAccess: true, + }) + async apiTokenList(@Request() req) { + return await getConditionalHandler( + this.orgTokensService.apiTokenList, + this.orgTokensEeService.apiTokenListEE, + )({ + query: req.query, + user: req['user'], + }); + } + + @Post('/api/v1/tokens') + @Acl('apiTokenCreate', { + blockApiTokenAccess: true, + }) + async apiTokenCreate(@Request() req, @Body() body: ApiTokenReqType) { + return await this.orgTokensService.apiTokenCreate({ + apiToken: body, + user: req['user'], + }); + } + + @Delete('/api/v1/tokens/:token') + @Acl('apiTokenDelete', { + // allowedRoles: [OrgUserRoles.SUPER], + blockApiTokenAccess: true, + }) + async apiTokenDelete(@Request() req, @Param('token') token: string) { + return; + await this.orgTokensService.apiTokenDelete({ + token, + user: req['user'], + }); + } +} diff --git a/packages/nocodb-nest/src/modules/org-tokens/org-tokens.module.ts b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.module.ts new file mode 100644 index 0000000000..1efdbf0158 --- /dev/null +++ b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { OrgTokensService } from './org-tokens.service'; +import { OrgTokensController } from './org-tokens.controller'; +import { OrgTokensEeService } from './ee/org-tokens/org-tokens.service'; + +@Module({ + controllers: [OrgTokensController], + providers: [OrgTokensEeService] +}) +export class OrgTokensModule {} diff --git a/packages/nocodb-nest/src/modules/org-tokens/org-tokens.service.spec.ts b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.service.spec.ts new file mode 100644 index 0000000000..10fbf006be --- /dev/null +++ b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { OrgTokensService } from './org-tokens.service'; + +describe('OrgTokensService', () => { + let service: OrgTokensService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [OrgTokensService], + }).compile(); + + service = module.get(OrgTokensService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/packages/nocodb-nest/src/modules/org-tokens/org-tokens.service.ts b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.service.ts new file mode 100644 index 0000000000..31bc2cb377 --- /dev/null +++ b/packages/nocodb-nest/src/modules/org-tokens/org-tokens.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; +import { ApiTokenReqType, OrgUserRoles } from 'nocodb-sdk'; +import { validatePayload } from '../../helpers'; +import { NcError } from '../../helpers/catchError'; +import { PagedResponseImpl } from '../../helpers/PagedResponse'; +import { ApiToken, User } from '../../models'; +import { T } from 'nc-help'; + +@Injectable() +export class OrgTokensService { + async apiTokenList(param: { user: User; query: any }) { + const fk_user_id = param.user.id; + let includeUnmappedToken = false; + if (param.user.roles.includes(OrgUserRoles.SUPER_ADMIN)) { + includeUnmappedToken = true; + } + + return new PagedResponseImpl( + await ApiToken.listWithCreatedBy({ + ...param.query, + fk_user_id, + includeUnmappedToken, + }), + { + ...param.query, + count: await ApiToken.count({ + includeUnmappedToken, + fk_user_id, + }), + }, + ); + } + + async apiTokenCreate(param: { user: User; apiToken: ApiTokenReqType }) { + validatePayload( + 'swagger.json#/components/schemas/ApiTokenReq', + param.apiToken, + ); + + T.emit('evt', { evt_type: 'org:apiToken:created' }); + return await ApiToken.insert({ + ...param.apiToken, + fk_user_id: param['user'].id, + }); + } + + async apiTokenDelete(param: { user: User; token: string }) { + const fk_user_id = param.user.id; + const apiToken = await ApiToken.getByToken(param.token); + if ( + !param.user.roles.includes(OrgUserRoles.SUPER_ADMIN) && + apiToken.fk_user_id !== fk_user_id + ) { + NcError.notFound('Token not found'); + } + T.emit('evt', { evt_type: 'org:apiToken:deleted' }); + return await ApiToken.delete(param.token); + } +}