refactor: login session with jwt

This commit is contained in:
Innei
2022-06-26 22:09:20 +08:00
parent e764dd5cce
commit 8408f2e63b
4 changed files with 65 additions and 39 deletions

View File

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

View File

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

View File

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

View File

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