feat: 使用 @nestjs-lib/auth 鉴权。
This commit is contained in:
		
							
								
								
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ivanli2048@gmail.com>",
 | 
			
		||||
  "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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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<string, string>) => {
 | 
			
		||||
            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('*');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {}
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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>(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();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -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'],
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {}
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user