feat: 使用中间件 从 jwt 中获取用户信息

This commit is contained in:
Ivan
2021-07-09 18:20:36 +08:00
parent 5ed17cc04b
commit 02059ee54f
15 changed files with 6721 additions and 4810 deletions

View File

@@ -1,3 +1,4 @@
import { CommonsModule } from './commons/commons.module';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { GraphQLModule } from '@nestjs/graphql';
@@ -12,13 +13,13 @@ import { PipelineTasksModule } from './pipeline-tasks/pipeline-tasks.module';
import configuration from './commons/config/configuration';
import { RedisModule } from 'nestjs-redis';
import { WebhooksModule } from './webhooks/webhooks.module';
import { RawBodyMiddleware } from './commons/middlewares/raw-body.middleware';
import { RawBodyMiddleware } from './commons/middleware/raw-body.middleware';
import { GiteaWebhooksController } from './webhooks/gitea-webhooks.controller';
import { ParseBodyMiddleware } from './commons/middlewares/parse-body.middleware';
import { BullModule } from '@nestjs/bull';
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';
@Module({
imports: [
@@ -66,17 +67,6 @@ import pinoPretty from 'pino-pretty';
}),
inject: [ConfigService],
}),
BullModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
redis: {
host: configService.get<string>('db.redis.host', 'localhost'),
port: configService.get<number>('db.redis.port', undefined),
password: configService.get<string>('db.redis.password', undefined),
},
}),
inject: [ConfigService],
}),
ProjectsModule,
ReposModule,
PipelinesModule,
@@ -91,7 +81,15 @@ import pinoPretty from 'pino-pretty';
}),
inject: [ConfigService],
}),
EtcdModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
hosts: configService.get<string>('db.etcd.hosts', 'localhost:2379'),
}),
inject: [ConfigService],
}),
WebhooksModule,
CommonsModule,
],
controllers: [AppController],
providers: [AppService, AppResolver],
@@ -102,6 +100,8 @@ export class AppModule implements NestModule {
.apply(RawBodyMiddleware)
.forRoutes(GiteaWebhooksController)
.apply(ParseBodyMiddleware)
.forRoutes('*')
.apply(AccountMiddleware)
.forRoutes('*');
}
}

View File

@@ -1,10 +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';
@Module({
providers: [PasswordConverter],
exports: [PasswordConverter, RedisMutexModule],
providers: [PasswordConverter, JwtService],
exports: [PasswordConverter, RedisMutexModule, JwtService],
imports: [RedisMutexModule],
})
export class CommonsModule {}

View File

@@ -0,0 +1,8 @@
import { JwtService } from '../services/jwt.service';
import { AccountMiddleware } from './account.middleware';
describe('AccountMiddleware', () => {
it('should be defined', () => {
expect(new AccountMiddleware({} as JwtService)).toBeDefined();
});
});

View File

@@ -0,0 +1,31 @@
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();
}
}

View File

@@ -0,0 +1,59 @@
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();
});
});
});

View File

@@ -0,0 +1,23 @@
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'],
});
}
}