Compare commits
No commits in common. "85b2062a2a84b2afb68b67013e729fdd536c3f45" and "b8cc65a7682cc4325fa26a2a5fc9f0c04af389f4" have entirely different histories.
85b2062a2a
...
b8cc65a768
@ -1,9 +1,9 @@
|
|||||||
import { BadGatewayException, Global, Module, ModuleMetadata, Type } from '@nestjs/common';
|
import { BadGatewayException, Global, Module, ModuleMetadata, Type } from '@nestjs/common';
|
||||||
|
|
||||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
|
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
|
||||||
import { useContainer } from 'class-validator';
|
import { useContainer } from 'class-validator';
|
||||||
|
|
||||||
import { isNil, omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
import { ConfigModule } from '@/modules/config/config.module';
|
import { ConfigModule } from '@/modules/config/config.module';
|
||||||
import { Configure } from '@/modules/config/configure';
|
import { Configure } from '@/modules/config/configure';
|
||||||
@ -85,13 +85,6 @@ export async function createBootModule(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNil(globals.guard)) {
|
|
||||||
providers.push({
|
|
||||||
provide: APP_GUARD,
|
|
||||||
useClass: globals.guard,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateModule('BootModule', () => ({
|
return CreateModule('BootModule', () => ({
|
||||||
imports,
|
imports,
|
||||||
providers,
|
providers,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { ModuleMetadata, PipeTransform, Type } from '@nestjs/common';
|
import { ModuleMetadata, PipeTransform, Type } from '@nestjs/common';
|
||||||
import { IAuthGuard } from '@nestjs/passport';
|
|
||||||
import { NestFastifyApplication } from '@nestjs/platform-fastify';
|
import { NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -29,11 +28,6 @@ export interface CreateOptions {
|
|||||||
interceptor?: Type<any> | null;
|
interceptor?: Type<any> | null;
|
||||||
|
|
||||||
filter?: Type<any> | null;
|
filter?: Type<any> | null;
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局守卫
|
|
||||||
*/
|
|
||||||
guard?: Type<IAuthGuard>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
providers?: ModuleMetadata['providers'];
|
providers?: ModuleMetadata['providers'];
|
||||||
|
@ -31,12 +31,3 @@ export enum UserOrderType {
|
|||||||
CREATED = 'createdAt',
|
CREATED = 'createdAt',
|
||||||
UPDATED = 'updatedAt',
|
UPDATED = 'updatedAt',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TokenConst = {
|
|
||||||
USER_TOKEN_SECRET: 'USER_TOKEN_SECRET',
|
|
||||||
DEFAULT_USER_TOKEN_SECRET: 'my-access-secret',
|
|
||||||
USER_REFRESH_TOKEN_EXPIRED: 'USER_REFRESH_TOKEN_EXPIRED',
|
|
||||||
DEFAULT_USER_REFRESH_TOKEN_EXPIRED: 'my-refresh-secret',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ALLOW_GUEST = 'allowGuest';
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { SetMetadata } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { ALLOW_GUEST } from '@/modules/user/constants';
|
|
||||||
|
|
||||||
export const Guest = () => SetMetadata(ALLOW_GUEST, true);
|
|
@ -1,8 +0,0 @@
|
|||||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { UserEntity } from '../entities/UserEntity';
|
|
||||||
|
|
||||||
export const RequestUser = createParamDecorator(async (_data: unknown, ctx: ExecutionContext) => {
|
|
||||||
const request = ctx.switchToHttp().getRequest();
|
|
||||||
return request.user as ClassToPlain<UserEntity>;
|
|
||||||
});
|
|
@ -1,82 +0,0 @@
|
|||||||
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
||||||
import { Reflector } from '@nestjs/core';
|
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
|
||||||
|
|
||||||
import { isNil } from 'lodash';
|
|
||||||
|
|
||||||
import { ExtractJwt } from 'passport-jwt';
|
|
||||||
|
|
||||||
import { TokenService } from '@/modules/user/services/token.service';
|
|
||||||
|
|
||||||
import { ALLOW_GUEST } from '../constants';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
|
||||||
constructor(
|
|
||||||
protected ref: Reflector,
|
|
||||||
protected tokenService: TokenService,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext) {
|
|
||||||
const allowGuest = this.ref.getAllAndOverride<boolean>(ALLOW_GUEST, [
|
|
||||||
context.getHandler(),
|
|
||||||
context.getClass(),
|
|
||||||
]);
|
|
||||||
if (allowGuest) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const request = this.getRequest(context);
|
|
||||||
|
|
||||||
const requestToken = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
|
|
||||||
if (isNil(requestToken)) {
|
|
||||||
throw new UnauthorizedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessToken = await this.tokenService.checkAccessToken(requestToken);
|
|
||||||
if (isNil(accessToken)) {
|
|
||||||
throw new UnauthorizedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (await super.canActivate(context)) as boolean;
|
|
||||||
} catch {
|
|
||||||
const response = this.getResponse(context);
|
|
||||||
const token = await this.tokenService.refreshToken(accessToken, response);
|
|
||||||
if (isNil(token)) {
|
|
||||||
throw new UnauthorizedException();
|
|
||||||
}
|
|
||||||
if (token.accessToken) {
|
|
||||||
request.headers.authorization = `Bearer ${token.accessToken.value}`;
|
|
||||||
}
|
|
||||||
return (await super.canActivate(context)) as boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动请求处理
|
|
||||||
* 如果请求中有错误则抛出错误
|
|
||||||
* 如果请求中没有用户信息则抛出401异常
|
|
||||||
* @param err
|
|
||||||
* @param user
|
|
||||||
* @param _info
|
|
||||||
*/
|
|
||||||
handleRequest(err: any, user: any, _info: any) {
|
|
||||||
if (err || isNil(user)) {
|
|
||||||
if (isNil(user)) {
|
|
||||||
throw new UnauthorizedException();
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRequest(context: ExecutionContext) {
|
|
||||||
return context.switchToHttp().getRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
getResponse(context: ExecutionContext) {
|
|
||||||
return context.switchToHttp().getResponse();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import { BadGatewayException, ExecutionContext, Injectable } from '@nestjs/common';
|
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
|
||||||
import { plainToClass } from 'class-transformer';
|
|
||||||
|
|
||||||
import { validateOrReject } from 'class-validator';
|
|
||||||
|
|
||||||
import { CredentialDto } from '../dtos/auth.dto';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户登录守卫
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
export class LocalAuthGuard extends AuthGuard('local') {
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
||||||
const request = context.switchToHttp().getRequest();
|
|
||||||
try {
|
|
||||||
await validateOrReject(plainToClass(CredentialDto, request.body), {
|
|
||||||
validationError: { target: false },
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
const messages = (error as any[])
|
|
||||||
.map((e) => e.constraints ?? {})
|
|
||||||
.reduce((o, n) => ({ ...o, ...n }), {});
|
|
||||||
throw new BadGatewayException(Object.values(messages));
|
|
||||||
}
|
|
||||||
return super.canActivate(context) as boolean;
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,8 +16,6 @@ import { AccessTokenEntity } from '@/modules/user/entities/access.token.entity';
|
|||||||
import { RefreshTokenEntity } from '@/modules/user/entities/refresh.token.entity';
|
import { RefreshTokenEntity } from '@/modules/user/entities/refresh.token.entity';
|
||||||
import { JwtConfig, JwtPayload, UserConfig } from '@/modules/user/types';
|
import { JwtConfig, JwtPayload, UserConfig } from '@/modules/user/types';
|
||||||
|
|
||||||
import { TokenConst } from '../constants';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 令牌服务
|
* 令牌服务
|
||||||
*/
|
*/
|
||||||
@ -87,10 +85,7 @@ export class TokenService {
|
|||||||
const refreshToken = new RefreshTokenEntity();
|
const refreshToken = new RefreshTokenEntity();
|
||||||
refreshToken.value = jwt.sign(
|
refreshToken.value = jwt.sign(
|
||||||
refreshTokenPayload,
|
refreshTokenPayload,
|
||||||
this.configure.env.get(
|
this.configure.env.get('USER_REFRESH_TOKEN_EXPIRED', 'my-refresh-secret'),
|
||||||
TokenConst.USER_REFRESH_TOKEN_EXPIRED,
|
|
||||||
TokenConst.DEFAULT_USER_REFRESH_TOKEN_EXPIRED,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
refreshToken.expiredAt = now.add(config.refreshTokenExpired, 'second').toDate();
|
refreshToken.expiredAt = now.add(config.refreshTokenExpired, 'second').toDate();
|
||||||
refreshToken.accessToken = accessToken;
|
refreshToken.accessToken = accessToken;
|
||||||
@ -141,10 +136,7 @@ export class TokenService {
|
|||||||
async verifyAccessToken(token: AccessTokenEntity) {
|
async verifyAccessToken(token: AccessTokenEntity) {
|
||||||
const result = jwt.verify(
|
const result = jwt.verify(
|
||||||
token.value,
|
token.value,
|
||||||
this.configure.env.get(
|
this.configure.env.get('USER_TOKEN_SECRET', 'my-access-secret'),
|
||||||
TokenConst.USER_TOKEN_SECRET,
|
|
||||||
TokenConst.DEFAULT_USER_TOKEN_SECRET,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
return token.user;
|
return token.user;
|
||||||
@ -160,10 +152,7 @@ export class TokenService {
|
|||||||
defaultUserConfig(configure),
|
defaultUserConfig(configure),
|
||||||
);
|
);
|
||||||
const options: JwtModuleOptions = {
|
const options: JwtModuleOptions = {
|
||||||
secret: configure.env.get(
|
secret: configure.env.get('USER_TOKEN_SECRET', 'my-access-secret'),
|
||||||
TokenConst.USER_TOKEN_SECRET,
|
|
||||||
TokenConst.DEFAULT_USER_TOKEN_SECRET,
|
|
||||||
),
|
|
||||||
verifyOptions: {
|
verifyOptions: {
|
||||||
ignoreExpiration: !configure.env.isProd(),
|
ignoreExpiration: !configure.env.isProd(),
|
||||||
},
|
},
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import { PassportStrategy } from '@nestjs/passport';
|
|
||||||
|
|
||||||
import { instanceToPlain } from 'class-transformer';
|
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
||||||
|
|
||||||
import { Configure } from '@/modules/config/configure';
|
|
||||||
|
|
||||||
import { TokenConst } from '../constants';
|
|
||||||
import { UserRepository } from '../repositories/UserRepository';
|
|
||||||
import { JwtPayload } from '../types';
|
|
||||||
|
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
|
||||||
constructor(
|
|
||||||
protected configure: Configure,
|
|
||||||
protected userRepository: UserRepository,
|
|
||||||
) {
|
|
||||||
const secret = configure.env.get(
|
|
||||||
TokenConst.USER_TOKEN_SECRET,
|
|
||||||
TokenConst.DEFAULT_USER_TOKEN_SECRET,
|
|
||||||
);
|
|
||||||
super({
|
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
||||||
ignoreExpiration: false,
|
|
||||||
secretOrKey: secret,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过荷载解析出用户ID
|
|
||||||
* 通过用户ID查询出用户是否存在,并把id放入request方便后续操作
|
|
||||||
* @param payload
|
|
||||||
*/
|
|
||||||
async validate(payload: JwtPayload) {
|
|
||||||
const user = await this.userRepository.findOneOrFail({ where: { id: payload.sub } });
|
|
||||||
return instanceToPlain(user);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
|
||||||
import { Strategy } from 'passport-local';
|
|
||||||
|
|
||||||
import { AuthService } from '../services/auth.service';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户认证本地策略
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
|
||||||
constructor(protected authService: AuthService) {
|
|
||||||
super({ usernameField: 'credential', passwordField: 'password' });
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate(credential: string, password: string): Promise<any> {
|
|
||||||
const user = await this.authService.validateUser(credential, password);
|
|
||||||
if (!user) {
|
|
||||||
throw new UnauthorizedException();
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,7 +16,6 @@ import { MeiliModule } from './modules/meilisearch/meili.module';
|
|||||||
import { Restful } from './modules/restful/restful';
|
import { Restful } from './modules/restful/restful';
|
||||||
import { RestfulModule } from './modules/restful/restful.module';
|
import { RestfulModule } from './modules/restful/restful.module';
|
||||||
import { ApiConfig } from './modules/restful/types';
|
import { ApiConfig } from './modules/restful/types';
|
||||||
import { JwtAuthGuard } from './modules/user/guards/jwt.auth.guard';
|
|
||||||
|
|
||||||
export const createOptions: CreateOptions = {
|
export const createOptions: CreateOptions = {
|
||||||
commands: () => [...Object.values(dbCommands)],
|
commands: () => [...Object.values(dbCommands)],
|
||||||
@ -27,7 +26,7 @@ export const createOptions: CreateOptions = {
|
|||||||
await RestfulModule.forRoot(configure),
|
await RestfulModule.forRoot(configure),
|
||||||
await ContentModule.forRoot(configure),
|
await ContentModule.forRoot(configure),
|
||||||
],
|
],
|
||||||
globals: { guard: JwtAuthGuard },
|
globals: {},
|
||||||
builder: async ({ configure, BootModule }) => {
|
builder: async ({ configure, BootModule }) => {
|
||||||
const container = await NestFactory.create<NestFastifyApplication>(
|
const container = await NestFactory.create<NestFastifyApplication>(
|
||||||
BootModule,
|
BootModule,
|
||||||
|
Loading…
Reference in New Issue
Block a user