diff --git a/package-lock.json b/package-lock.json index 5100695..97cdb4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "fennec-be", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.1.0", + "version": "0.1.1", "license": "UNLICENSED", "dependencies": { "@golevelup/nestjs-rabbitmq": "^1.16.1", + "@nestjs-lib/auth": "^0.1.1", "@nestjs/common": "^7.5.1", "@nestjs/config": "^0.6.2", "@nestjs/core": "^7.5.1", @@ -2892,6 +2893,18 @@ "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==" }, + "node_modules/@nestjs-lib/auth": { + "version": "0.1.1", + "resolved": "https://npm.ivanli.cc/@nestjs-lib%2fauth/-/auth-0.1.1.tgz", + "integrity": "sha512-JXKvDsJudBlEXBiGyoODFpbbJabcoSaUqJY0bQHX0imidmhovx3VuGZwudALrlw1BT2NOJEO7ElFoITCfTDfGw==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^7.0.0", + "@nestjs/graphql": "^7.10.3", + "jose": "^3.14.0", + "nestjs-etcd": "^0.2.0" + } + }, "node_modules/@nestjs/cli": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-7.6.0.tgz", @@ -18822,6 +18835,12 @@ "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==" }, + "@nestjs-lib/auth": { + "version": "0.1.1", + "resolved": "https://npm.ivanli.cc/@nestjs-lib%2fauth/-/auth-0.1.1.tgz", + "integrity": "sha512-JXKvDsJudBlEXBiGyoODFpbbJabcoSaUqJY0bQHX0imidmhovx3VuGZwudALrlw1BT2NOJEO7ElFoITCfTDfGw==", + "requires": {} + }, "@nestjs/cli": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-7.6.0.tgz", diff --git a/package.json b/package.json index f70a0ed..6cf3f92 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "fennec-be", - "version": "0.1.0", - "description": "", - "author": "", + "version": "0.1.1", + "description": "a ci/cd project.", + "author": "Ivan Li\b", "private": true, "license": "UNLICENSED", "scripts": { @@ -22,6 +22,7 @@ }, "dependencies": { "@golevelup/nestjs-rabbitmq": "^1.16.1", + "@nestjs-lib/auth": "^0.1.1", "@nestjs/common": "^7.5.1", "@nestjs/config": "^0.6.2", "@nestjs/core": "^7.5.1", diff --git a/src/app.module.ts b/src/app.module.ts index ec5c71c..58440a8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -19,7 +19,7 @@ import { ParseBodyMiddleware } from './commons/middleware/parse-body.middleware' import { LoggerModule } from 'nestjs-pino'; import { EtcdModule } from 'nestjs-etcd'; import pinoPretty from 'pino-pretty'; -import { AccountMiddleware } from './commons/middleware/account.middleware'; +import { fromPairs, map, pipe, toPairs } from 'ramda'; @Module({ imports: [ @@ -64,6 +64,22 @@ import { AccountMiddleware } from './commons/middleware/account.middleware'; playground: true, autoSchemaFile: true, installSubscriptionHandlers: true, + context: ({ req, connection, ...args }) => { + return connection ? { req: connection.context } : { req }; + }, + subscriptions: { + onConnect: (connectionParams: Record) => { + const connectionParamsWithLowerKeys = pipe( + toPairs, + map(([key, value]) => [key.toLowerCase(), value]), + fromPairs, + )(connectionParams); + + return { + headers: connectionParamsWithLowerKeys, + }; + }, + }, }), inject: [ConfigService], }), @@ -100,8 +116,6 @@ export class AppModule implements NestModule { .apply(RawBodyMiddleware) .forRoutes(GiteaWebhooksController) .apply(ParseBodyMiddleware) - .forRoutes('*') - .apply(AccountMiddleware) .forRoutes('*'); } } diff --git a/src/commons/commons.module.ts b/src/commons/commons.module.ts index d4a552a..7767952 100644 --- a/src/commons/commons.module.ts +++ b/src/commons/commons.module.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { PasswordConverter } from './services/password-converter'; import { RedisMutexModule } from './redis-mutex/redis-mutex.module'; -import { JwtService } from './services/jwt.service'; +import { AuthModule } from '@nestjs-lib/auth'; @Module({ - providers: [PasswordConverter, JwtService], - exports: [PasswordConverter, RedisMutexModule, JwtService], - imports: [RedisMutexModule], + imports: [RedisMutexModule, AuthModule], + providers: [PasswordConverter], + exports: [PasswordConverter, RedisMutexModule], }) export class CommonsModule {} diff --git a/src/commons/middleware/account.middleware.spec.ts b/src/commons/middleware/account.middleware.spec.ts deleted file mode 100644 index 2b8d529..0000000 --- a/src/commons/middleware/account.middleware.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { JwtService } from '../services/jwt.service'; -import { AccountMiddleware } from './account.middleware'; - -describe('AccountMiddleware', () => { - it('should be defined', () => { - expect(new AccountMiddleware({} as JwtService)).toBeDefined(); - }); -}); diff --git a/src/commons/middleware/account.middleware.ts b/src/commons/middleware/account.middleware.ts deleted file mode 100644 index 1a97095..0000000 --- a/src/commons/middleware/account.middleware.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - Injectable, - NestMiddleware, - UnauthorizedException, -} from '@nestjs/common'; -import { JwtService } from '../services/jwt.service'; - -@Injectable() -export class AccountMiddleware implements NestMiddleware { - constructor(private readonly jwtService: JwtService) {} - async use(req: any, res: any, next: () => void) { - const authPayload = req.header('authorization') ?? ''; - if (!authPayload) { - req.user = req.session?.user; - next(); - return; - } - const token = authPayload.replace('Bearer ', ''); - if (!token) { - throw new UnauthorizedException('授权凭据不合法!'); - } - try { - const { payload } = await this.jwtService.verify(token); - req.user = payload; - next(); - } catch (err) { - throw new UnauthorizedException('登录凭据失效或不合法!'); - } - next(); - } -} diff --git a/src/commons/services/jwt.service.spec.ts b/src/commons/services/jwt.service.spec.ts deleted file mode 100644 index fe3c264..0000000 --- a/src/commons/services/jwt.service.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { generateKeyPair, KeyObject } from 'crypto'; -import { getClientToken } from 'nestjs-etcd'; -import { promisify } from 'util'; -import { JwtService } from './jwt.service'; -import { SignJWT } from 'jose/jwt/sign'; - -describe('JwtService', () => { - let service: JwtService; - let privateKey: KeyObject; - let publicKey: KeyObject; - - beforeAll(async () => { - const pair = await promisify(generateKeyPair)('ec', { - namedCurve: 'prime256v1', - }); - privateKey = pair.privateKey; - publicKey = pair.publicKey; - }); - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - JwtService, - { - provide: getClientToken(), - useValue: { - get: () => ({ - buffer: () => - Promise.resolve( - publicKey.export({ format: 'pem', type: 'spki' }), - ), - }), - }, - }, - ], - }).compile(); - - service = module.get(JwtService); - await service.onModuleInit(); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - describe('verify', () => { - it('normal', async () => { - const token = await new SignJWT({ userId: 'test' }) - .setProtectedHeader({ alg: 'ES256' }) - .setIssuedAt() - .setIssuer('urn:example:issuer') - .setAudience('urn:example:audience') - .setExpirationTime('1h') - .sign(privateKey); - await expect(service.verify(token)).resolves.toBeTruthy(); - }); - }); -}); diff --git a/src/commons/services/jwt.service.ts b/src/commons/services/jwt.service.ts deleted file mode 100644 index 9886596..0000000 --- a/src/commons/services/jwt.service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { OnModuleInit } from '@nestjs/common'; -import { Injectable } from '@nestjs/common'; -import { KeyObject, createPublicKey } from 'crypto'; -import { jwtVerify } from 'jose/jwt/verify'; -import { Etcd3, InjectClient } from 'nestjs-etcd'; - -@Injectable() -export class JwtService implements OnModuleInit { - publicKey: KeyObject; - constructor(@InjectClient() private readonly etcd: Etcd3) {} - async onModuleInit() { - const buff = await this.etcd - .get('commons/auth-jwt-public-key/index') - .buffer(); - this.publicKey = createPublicKey(buff); - } - - async verify(token: string) { - return await jwtVerify(token, this.publicKey, { - algorithms: ['PS256', 'ES256'], - }); - } -} diff --git a/src/pipeline-tasks/pipeline-tasks.resolver.ts b/src/pipeline-tasks/pipeline-tasks.resolver.ts index 5702282..4b242cc 100644 --- a/src/pipeline-tasks/pipeline-tasks.resolver.ts +++ b/src/pipeline-tasks/pipeline-tasks.resolver.ts @@ -7,7 +7,9 @@ import { plainToClass } from 'class-transformer'; import { PipelineTaskLogger } from './pipeline-task.logger'; import { observableToAsyncIterable } from '@graphql-tools/utils'; import { PipelineTaskEvent } from './models/pipeline-task-event'; +import { Roles, AccountRole } from '@nestjs-lib/auth'; +@Roles(AccountRole.admin, AccountRole.super) @Resolver() export class PipelineTasksResolver { constructor( diff --git a/src/pipelines/commit-logs.resolver.ts b/src/pipelines/commit-logs.resolver.ts index 2755767..f42d9e4 100644 --- a/src/pipelines/commit-logs.resolver.ts +++ b/src/pipelines/commit-logs.resolver.ts @@ -1,3 +1,4 @@ +import { Roles, AccountRole } from '@nestjs-lib/auth'; import { Query } from '@nestjs/graphql'; import { Args, @@ -10,6 +11,7 @@ import { PipelineTasksService } from '../pipeline-tasks/pipeline-tasks.service'; import { Commit, LogFields } from '../repos/dtos/log-list.model'; import { PipelinesService } from './pipelines.service'; +@Roles(AccountRole.admin, AccountRole.super) @Resolver(() => Commit) export class CommitLogsResolver { constructor( diff --git a/src/pipelines/pipelines.resolver.ts b/src/pipelines/pipelines.resolver.ts index 43ef7ad..eb196d3 100644 --- a/src/pipelines/pipelines.resolver.ts +++ b/src/pipelines/pipelines.resolver.ts @@ -4,7 +4,9 @@ import { UpdatePipelineInput } from './dtos/update-pipeline.input'; import { Pipeline } from './pipeline.entity'; import { PipelinesService } from './pipelines.service'; import { ListPipelineArgs } from './dtos/list-pipelines.args'; +import { Roles, AccountRole } from '@nestjs-lib/auth'; +@Roles(AccountRole.admin, AccountRole.super) @Resolver() export class PipelinesResolver { constructor(private readonly service: PipelinesService) {} diff --git a/src/projects/projects.resolver.ts b/src/projects/projects.resolver.ts index 44a19fd..df0a5eb 100644 --- a/src/projects/projects.resolver.ts +++ b/src/projects/projects.resolver.ts @@ -1,9 +1,11 @@ +import { AccountRole, Roles } from '@nestjs-lib/auth'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { CreateProjectInput } from './dtos/create-project.input'; import { UpdateProjectInput } from './dtos/update-project.input'; import { Project } from './project.entity'; import { ProjectsService } from './projects.service'; +@Roles(AccountRole.admin, AccountRole.super) @Resolver(() => Project) export class ProjectsResolver { constructor(private readonly service: ProjectsService) {}