From 8408f2e63bab53fc42d55439f71a7594ea17bd66 Mon Sep 17 00:00:00 2001 From: Innei Date: Sun, 26 Jun 2022 22:09:20 +0800 Subject: [PATCH] refactor: login session with jwt --- src/modules/auth/auth.controller.ts | 9 ---- src/modules/user/user.controller.ts | 54 ++++++++++++++++----- src/modules/user/user.service.ts | 14 ------ src/processors/helper/helper.jwt.service.ts | 27 +++++++++-- 4 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index bee8f206..5baf4f31 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -16,12 +16,10 @@ import { Query, } from '@nestjs/common' import { EventEmitter2 } from '@nestjs/event-emitter' -import { ApiBearerAuth, ApiOperation } from '@nestjs/swagger' import { ApiController } from '~/common/decorator/api-controller.decorator' import { Auth } from '~/common/decorator/auth.decorator' import { ApiName } from '~/common/decorator/openapi.decorator' -import { IsMaster as Master } from '~/common/decorator/role.decorator' import { EventBusEvents } from '~/constants/event-bus.constant' import { MongoIdDto } from '~/shared/dto/id.dto' @@ -47,13 +45,6 @@ export class AuthController { private readonly eventEmitter: EventEmitter2, ) {} - @Get() - @ApiOperation({ summary: '判断当前 Token 是否有效 ' }) - @ApiBearerAuth() - checkLogged(@Master() isMaster: boolean) { - return { ok: ~~isMaster, isGuest: !isMaster } - } - @Get('token') @Auth() async getOrVerifyToken( diff --git a/src/modules/user/user.controller.ts b/src/modules/user/user.controller.ts index bcd28a03..693a751c 100644 --- a/src/modules/user/user.controller.ts +++ b/src/modules/user/user.controller.ts @@ -1,4 +1,4 @@ -import { Body, Get, HttpCode, Patch, Post } from '@nestjs/common' +import { Body, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common' import { ApiOperation } from '@nestjs/swagger' import { ApiController } from '~/common/decorator/api-controller.decorator' @@ -28,22 +28,35 @@ export class UserController { ) {} @Get() - @ApiOperation({ summary: '获取主人信息' }) async getMasterInfo(@IsMaster() isMaster: boolean) { return await this.userService.getMasterInfo(isMaster) } - @Post('register') - @ApiOperation({ summary: '注册' }) + @Post('/register') async register(@Body() userDto: UserDto) { userDto.name = userDto.name ?? userDto.username return await this.userService.createMaster(userDto as UserModel) } - @Post('login') - @ApiOperation({ summary: '登录' }) + @Put('/login') + @Auth() + async loginWithToken( + @IpLocation() ipLocation: IpRecord, + @CurrentUser() user: UserDocument, + @CurrentUserToken() token: string, + ) { + await this.authService.jwtServicePublic.revokeToken(token) + await this.userService.recordFootstep(ipLocation.ip) + return { + token: this.authService.jwtServicePublic.sign(user._id, { + ip: ipLocation.ip, + ua: ipLocation.agent, + }), + } + } + + @Post('/login') @HttpCache({ disable: true }) - @HttpCode(200) async login(@Body() dto: LoginDto, @IpLocation() ipLocation: IpRecord) { const user = await this.userService.login(dto.username, dto.password) const footstep = await this.userService.recordFootstep(ipLocation.ip) @@ -51,7 +64,10 @@ export class UserController { const avatar = user.avatar ?? getAvatar(mail) return { - token: this.authService.jwtServicePublic.sign(user._id), + token: this.authService.jwtServicePublic.sign(user._id, { + ip: ipLocation.ip, + ua: ipLocation.agent, + }), ...footstep, name, username, @@ -65,14 +81,12 @@ export class UserController { @Get('check_logged') @ApiOperation({ summary: '判断当前 Token 是否有效 ' }) - @Auth() @HttpCache.disable checkLogged(@IsMaster() isMaster: boolean) { return { ok: +isMaster, isGuest: !isMaster } } @Patch() - @ApiOperation({ summary: '修改主人的信息' }) @Auth() @HttpCache.disable @BanInDemo @@ -83,9 +97,27 @@ export class UserController { return await this.userService.patchUserData(user, body) } - @Post('logout') + @Post('/logout') @Auth() async singout(@CurrentUserToken() token: string) { return this.userService.signout(token) } + + @Get('/session') + @Auth() + async getAllSession(@CurrentUserToken() token: string) { + return this.authService.jwtServicePublic.getAllSignSession(token) + } + + @Delete('/session/:tokenId') + @Auth() + async deleteSession(@Param('tokenId') tokenId: string) { + return this.authService.jwtServicePublic.revokeToken(tokenId) + } + + @Delete('/session/all') + @Auth() + async deleteAllSession() { + return this.authService.jwtServicePublic.revokeAll() + } } diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index 1749053a..d3d6b3d7 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -11,12 +11,10 @@ import { import { ReturnModelType } from '@typegoose/typegoose' import { BusinessException } from '~/common/exceptions/business.exception' -import { RedisKeys } from '~/constants/cache.constant' import { ErrorCodeEnum } from '~/constants/error-code.constant' import { CacheService } from '~/processors/redis/cache.service' import { InjectModel } from '~/transformers/model.transformer' import { getAvatar, sleep } from '~/utils' -import { getRedisKey } from '~/utils/redis.util' import { AuthService } from '../auth/auth.service' import { UserDocument, UserModel } from './user.model' @@ -80,7 +78,6 @@ export class UserService { throw new BadRequestException('我已经有一个主人了哦') } - // @ts-ignore const res = await this.userModel.create({ ...model }) const token = this.authService.jwtServicePublic.sign(res._id) return { token, username: res.username } @@ -142,19 +139,8 @@ export class UserService { lastLoginTime: new Date(), lastLoginIp: ip, }) - // save to redis - process.nextTick(async () => { - const redisClient = this.redis.getClient() - - await redisClient.sadd( - getRedisKey(RedisKeys.LoginRecord), - JSON.stringify({ date: new Date().toISOString(), ip }), - ) - }) this.Logger.warn(`主人已登录, IP: ${ip}`) return PrevFootstep as any } - - // TODO 获取最近登陆次数 时间 从 Redis 取 } diff --git a/src/processors/helper/helper.jwt.service.ts b/src/processors/helper/helper.jwt.service.ts index 46ce86ba..87c8097b 100644 --- a/src/processors/helper/helper.jwt.service.ts +++ b/src/processors/helper/helper.jwt.service.ts @@ -65,10 +65,26 @@ export class JWTService { return !!has } + async getAllSignSession(currentToken?: string) { + const redis = this.cacheService.getClient() + const res = await redis.hgetall(getRedisKey(RedisKeys.JWTStore)) + const hashedCurrent = currentToken && md5(currentToken) + return Object.entries(res).map(([k, v]) => { + return { + ...JSON.parse(v), + id: `jwt-${k}`, + current: hashedCurrent === k, + } + }) + } + async revokeToken(token: string) { const redis = this.cacheService.getClient() const key = getRedisKey(RedisKeys.JWTStore) - await redis.hdel(key, md5(token)) + await redis.hdel( + key, + token.startsWith(`jwt-`) ? token.replace(`jwt-`, '') : md5(token), + ) } async revokeAll() { @@ -77,22 +93,23 @@ export class JWTService { await redis.del(key) } - async storeTokenInRedis(token: string) { + async storeTokenInRedis(token: string, info?: any) { const redis = this.cacheService.getClient() await redis.hset( getRedisKey(RedisKeys.JWTStore), md5(token), JSON.stringify({ date: new Date().toISOString(), + ...info, }), ) } - sign(id: string) { + sign(id: string, info?: { ip: string; ua: string }) { const token = sign({ id }, this.secret, { - expiresIn: '1y', + expiresIn: '30d', }) - this.storeTokenInRedis(token) + this.storeTokenInRedis(token, info || {}) return token } }