feat: 使用 @nestjs-lib/auth 鉴权。
This commit is contained in:
@@ -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