feat: use cookie to authentication

This commit is contained in:
fantasticit 2022-05-22 23:40:22 +08:00
parent f1cb1012f2
commit 49141c6566
9 changed files with 49 additions and 32 deletions

View File

@ -36,6 +36,7 @@
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.13.2", "class-validator": "^0.13.2",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"express": "^4.17.2", "express": "^4.17.2",
"express-rate-limit": "^6.2.0", "express-rate-limit": "^6.2.0",
@ -64,6 +65,7 @@
"@nestjs/cli": "^8.0.0", "@nestjs/cli": "^8.0.0",
"@nestjs/schematics": "^8.0.0", "@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0", "@nestjs/testing": "^8.0.0",
"@types/cookie-parser": "^1.4.3",
"@types/cron": "^2.0.0", "@types/cron": "^2.0.0",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/jest": "27.0.2", "@types/jest": "27.0.2",

View File

@ -12,10 +12,13 @@ import {
Patch, Patch,
Post, Post,
Request, Request,
Res,
UseGuards, UseGuards,
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { wrapResponse } from '@transforms/http-response.transform';
import { Response as ExpressResponse } from 'express';
@Controller('user') @Controller('user')
export class UserController { export class UserController {
@ -31,9 +34,16 @@ export class UserController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Post('login') @Post('login')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
async login(@Body() user: LoginUserDto) { async login(@Body() user: LoginUserDto, @Res({ passthrough: true }) response: ExpressResponse) {
const res = await this.userService.login(user); const { user: data, token } = await this.userService.login(user);
return res; response.cookie('token', token, { httpOnly: true, sameSite: 'none', secure: true });
return response.send(wrapResponse({ data: { ...data, token }, statusCode: HttpStatus.OK }));
}
@Get('logout')
async logout(@Res({ passthrough: true }) response: ExpressResponse) {
response.cookie('token', '', { expires: new Date() });
return;
} }
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)

View File

@ -253,6 +253,10 @@ export class WikiController {
@UseGuards(WikiUserRoleGuard) @UseGuards(WikiUserRoleGuard)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getWikiTocs(@Request() req, @Param('id') wikiId) { async getWikiTocs(@Request() req, @Param('id') wikiId) {
const sleep = (v) => {
return new Promise((r) => setTimeout(r, v * 1000));
};
await sleep(4);
return await this.wikiService.getWikiTocs(req.user, wikiId); return await this.wikiService.getWikiTocs(req.user, wikiId);
} }

View File

@ -23,13 +23,7 @@ export class DocumentAuthorityGuard implements CanActivate {
} }
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
const token = request?.cookies['token'];
let token = request.headers.authorization;
if (/Bearer/.test(token)) {
token = token.split(' ').pop();
}
const user = this.jwtService.decode(token) as IUser; const user = this.jwtService.decode(token) as IUser;
const { params, query, body } = request; const { params, query, body } = request;
const documentId = params?.id || params?.documentId || query?.id || query?.documentId || body?.documentId; const documentId = params?.id || params?.documentId || query?.id || query?.documentId || body?.documentId;

View File

@ -23,15 +23,8 @@ export class WikiUserRoleGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
const targetUserRole = this.reflector.get<WikiUserRole | null>(KEY, context.getHandler()); const targetUserRole = this.reflector.get<WikiUserRole | null>(KEY, context.getHandler());
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
const token = request?.cookies['token'];
let token = request.headers.authorization;
if (/Bearer/.test(token)) {
token = token.split(' ').pop();
}
const user = this.jwtService.decode(token) as IUser; const user = this.jwtService.decode(token) as IUser;
if (!user) { if (!user) {

View File

@ -5,6 +5,7 @@ import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@pipes/validation.pipe'; import { ValidationPipe } from '@pipes/validation.pipe';
import { HttpResponseTransformInterceptor } from '@transforms/http-response.transform'; import { HttpResponseTransformInterceptor } from '@transforms/http-response.transform';
import * as compression from 'compression'; import * as compression from 'compression';
import * as cookieParser from 'cookie-parser';
import * as express from 'express'; import * as express from 'express';
import helmet from 'helmet'; import helmet from 'helmet';
@ -18,7 +19,13 @@ async function bootstrap() {
const config = app.get(ConfigService); const config = app.get(ConfigService);
const port = config.get('server.port') || 5002; const port = config.get('server.port') || 5002;
app.enableCors(); app.enableCors({
// TODO: fixme
origin: 'http://localhost:5001',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
credentials: true,
});
app.use(cookieParser());
app.use(compression()); app.use(compression());
app.use(helmet()); app.use(helmet());
app.use(express.json()); app.use(express.json());

View File

@ -10,6 +10,7 @@ import { PassportModule, PassportStrategy } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { getConfig } from '@think/config'; import { getConfig } from '@think/config';
import { Request as RequestType } from 'express';
import { ExtractJwt, Strategy } from 'passport-jwt'; import { ExtractJwt, Strategy } from 'passport-jwt';
const config = getConfig(); const config = getConfig();
@ -25,8 +26,14 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
private readonly userService: UserService private readonly userService: UserService
) { ) {
super({ super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false,
secretOrKey: jwtConfig.secretkey, secretOrKey: jwtConfig.secretkey,
jwtFromRequest: ExtractJwt.fromExtractors([
(request: RequestType) => {
const token = request?.cookies['token'];
return token;
},
]),
}); });
} }

View File

@ -111,7 +111,7 @@ export class UserService {
* @param user * @param user
* @returns * @returns
*/ */
async login(user: LoginUserDto): Promise<OutUser & { token: string }> { async login(user: LoginUserDto): Promise<{ user: OutUser; token: string }> {
const { name, password } = user; const { name, password } = user;
const existUser = await this.userRepo.findOne({ where: { name } }); const existUser = await this.userRepo.findOne({ where: { name } });
@ -125,7 +125,7 @@ export class UserService {
const res = instanceToPlain(existUser) as OutUser; const res = instanceToPlain(existUser) as OutUser;
const token = this.jwtService.sign(res); const token = this.jwtService.sign(res);
return Object.assign(res, { token }); return { user: res, token };
} }
async validateUser(user: UserEntity) { async validateUser(user: UserEntity) {

View File

@ -6,6 +6,15 @@ interface Response<T> {
data: T; data: T;
} }
export function wrapResponse({ statusCode, data }) {
return {
statusCode,
message: null,
success: true,
data,
};
}
@Injectable() @Injectable()
export class HttpResponseTransformInterceptor<T> implements NestInterceptor<T, Response<T>> { export class HttpResponseTransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<Response<T>> { intercept(context: ExecutionContext, next: CallHandler<T>): Observable<Response<T>> {
@ -13,17 +22,8 @@ export class HttpResponseTransformInterceptor<T> implements NestInterceptor<T, R
map((data) => { map((data) => {
const ctx = context.switchToHttp(); const ctx = context.switchToHttp();
const response = ctx.getResponse(); const response = ctx.getResponse();
// const request = ctx.getRequest();
// const url = request.originalUrl;
const statusCode = response.statusCode; const statusCode = response.statusCode;
const res = { return wrapResponse({ data, statusCode });
statusCode,
message: null,
success: true,
data,
};
// console.info(url, res);
return res;
}) })
); );
} }