feat: 使用 @nestjs-lib/auth 鉴权。

This commit is contained in:
Ivan Li
2021-07-18 22:40:33 +08:00
parent ec351d12f2
commit 0a03bcd36e
12 changed files with 54 additions and 133 deletions

View File

@@ -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('*');
}
}

View File

@@ -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 {}

View File

@@ -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();
});
});

View File

@@ -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();
}
}

View File

@@ -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();
});
});
});

View File

@@ -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'],
});
}
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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) {}

View File

@@ -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) {}