From 46253b3a4f6abb3351a4e66ca25f22b3ffb8d327 Mon Sep 17 00:00:00 2001 From: liuyi Date: Sun, 22 Jun 2025 14:27:15 +0800 Subject: [PATCH] add user module --- src/modules/user/config.ts | 4 +- .../user/entities/access.token.entity.ts | 2 +- src/modules/user/entities/base.token.ts | 2 +- src/modules/user/services/token.service.ts | 142 ++++++++++++++++++ src/modules/user/types.ts | 4 +- 5 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 src/modules/user/services/token.service.ts diff --git a/src/modules/user/config.ts b/src/modules/user/config.ts index 4d31dfb..1e6fb55 100644 --- a/src/modules/user/config.ts +++ b/src/modules/user/config.ts @@ -12,8 +12,8 @@ export function defaultUserConfig(configure: Configure): UserConfig { return { hash: 10, jwt: { - token_expired: configure.env.get('USER_TOKEN_EXPIRED', (v) => toNumber(v), 1800), - refresh_token_expired: configure.env.get( + tokenExpired: configure.env.get('USER_TOKEN_EXPIRED', (v) => toNumber(v), 1800), + refreshTokenExpired: configure.env.get( 'USER_REFRESH_TOKEN_EXPIRED', (v) => toNumber(v), 3600 * 30, diff --git a/src/modules/user/entities/access.token.entity.ts b/src/modules/user/entities/access.token.entity.ts index 469e1cc..e172e23 100644 --- a/src/modules/user/entities/access.token.entity.ts +++ b/src/modules/user/entities/access.token.entity.ts @@ -13,7 +13,7 @@ export class AccessTokenEntity extends BaseToken { * 关联的刷新令牌 */ @OneToOne(() => RefreshTokenEntity, (token) => token.accessToken, { cascade: true }) - refreshToken: string; + refreshToken: RefreshTokenEntity; /** * 关联用户 diff --git a/src/modules/user/entities/base.token.ts b/src/modules/user/entities/base.token.ts index d0a2d32..dddc735 100644 --- a/src/modules/user/entities/base.token.ts +++ b/src/modules/user/entities/base.token.ts @@ -15,7 +15,7 @@ export abstract class BaseToken extends BaseEntity { @Column({ comment: '令牌过期时间', }) - expired_at: Date; + expiredAt: Date; @CreateDateColumn({ comment: '令牌创建时间', diff --git a/src/modules/user/services/token.service.ts b/src/modules/user/services/token.service.ts new file mode 100644 index 0000000..ab09da3 --- /dev/null +++ b/src/modules/user/services/token.service.ts @@ -0,0 +1,142 @@ +import { Injectable } from '@nestjs/common'; + +import { JwtService } from '@nestjs/jwt'; + +import dayjs from 'dayjs'; + +import { Configure } from '@/modules/config/configure'; +import { getTime } from '@/modules/core/helpers/time'; +import { getUserConfig } from '@/modules/user/config'; +import { UserEntity } from '@/modules/user/entities/UserEntity'; +import { AccessTokenEntity } from '@/modules/user/entities/access.token.entity'; +import { RefreshTokenEntity } from '@/modules/user/entities/refresh.token.entity'; +import { JwtConfig, JwtPayload } from '@/modules/user/types'; + +/** + * 令牌服务 + */ +@Injectable() +export class TokenService { + constructor( + protected configure: Configure, + protected jwtService: JwtService, + ) {} + + /** + * 根据accessToken刷新AccessToken与RefreshToken + * @param accessToken + * @param response + */ + async refreshToken(accessToken: AccessTokenEntity, response: Response) { + const { user, refreshToken } = accessToken; + if (refreshToken) { + const now = await getTime(this.configure); + if (now.isAfter(refreshToken.expiredAt)) { + return null; + } + const token = await this.generateAccessToken(user, now); + await accessToken.remove(); + response.header('token', token.accessToken.value); + return token; + } + return null; + } + + /** + * 根据荷载签出新的AccessToken并存入数据库 + * 且自动生成新的Refresh也存入数据库 + * @param user + * @param now + */ + async generateAccessToken( + user: UserEntity, + now: dayjs.Dayjs, + ): Promise<{ accessToken: AccessTokenEntity; refreshToken: RefreshTokenEntity }> { + const config = await getUserConfig(this.configure, 'jwt'); + const accessTokenPayload: JwtPayload = { sub: user.id, iat: now.unix() }; + const signed = this.jwtService.sign(accessTokenPayload); + const accessToken = new AccessTokenEntity(); + accessToken.value = signed; + accessToken.user = user; + accessToken.expiredAt = now.add(config.tokenExpired, 'second').toDate(); + await accessToken.save(); + const refreshToken = await this.generateRefreshToken( + accessToken, + await getTime(this.configure), + ); + return { accessToken, refreshToken }; + } + + /** + * 生成新的RefreshToken并存入数据库 + * @param accessToken + * @param now + */ + async generateRefreshToken( + accessToken: AccessTokenEntity, + now: dayjs.Dayjs, + ): Promise { + const config = await getUserConfig(this.configure, 'jwt'); + const refreshTokenPayload = { uuid: uuid() }; + const refreshToken = new RefreshTokenEntity(); + refreshToken.value = this.jwtService.sign( + refreshTokenPayload, + this.configure.env.get('USER_REFRESH_TOKEN_EXPIRED', 'my-refresh-secret'), + ); + refreshToken.expiredAt = now.add(config.refreshTokenExpired, 'second').toDate(); + refreshToken.accessToken = accessToken; + await refreshToken.save(); + return refreshToken; + } + + /** + * 检查accessToken是否存在 + * @param value + */ + async checkAccessToken(value: string) { + return AccessTokenEntity.findOne({ where: { value }, relations: ['user', 'refreshToken'] }); + } + + /** + * 移除AccessToken且自动移除关联的RefreshToken + * @param value + */ + async removeAccessToken(value: string) { + const accessToken = await AccessTokenEntity.findOne({ where: { value } }); + if (accessToken) { + await accessToken.remove(); + } + } + + /** + * 移除RefreshToken + * @param value + */ + async removeRefreshToken(value: string) { + const refreshToken = await RefreshTokenEntity.findOne({ + where: { value }, + relations: ['accessToken'], + }); + if (refreshToken) { + if (refreshToken.accessToken) { + await refreshToken.accessToken.remove(); + } + await refreshToken.remove(); + } + } + + /** + * 验证Token是否正确,如果正确则返回所属用户对象 + * @param token + */ + async verifyAccessToken(token: AccessTokenEntity) { + const result = this.jwtService.verify( + token.value, + this.configure.env.get('USER_TOKEN_SECRET', 'my-access-secret'), + ); + if (result) { + return token.user; + } + return null; + } +} diff --git a/src/modules/user/types.ts b/src/modules/user/types.ts index 5d30c8b..cfa2e5a 100644 --- a/src/modules/user/types.ts +++ b/src/modules/user/types.ts @@ -19,11 +19,11 @@ export interface JwtConfig { /** * token过期时间 */ - token_expired: number; + tokenExpired: number; /** * refresh token */ - refresh_token_expired: number; + refreshTokenExpired: number; } /**