nestapp/src/modules/user/services/token.service.ts
2025-06-22 17:57:19 +08:00

179 lines
5.8 KiB
TypeScript

/* eslint-disable import/no-extraneous-dependencies */
import { Injectable } from '@nestjs/common';
import { JwtModule, JwtModuleOptions, JwtService } from '@nestjs/jwt';
import dayjs from 'dayjs';
import { FastifyReply as Response } from 'fastify';
import jwt from 'jsonwebtoken';
import { v4 as uuid } from 'uuid';
import { Configure } from '@/modules/config/configure';
import { getTime } from '@/modules/core/helpers/time';
import { defaultUserConfig, 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, UserConfig } from '@/modules/user/types';
import { TokenConst } from '../constants';
/**
* 令牌服务
*/
@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<JwtConfig>(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<RefreshTokenEntity> {
const config = await getUserConfig<JwtConfig>(this.configure, 'jwt');
const refreshTokenPayload = { uuid: uuid() };
const refreshToken = new RefreshTokenEntity();
refreshToken.value = jwt.sign(
refreshTokenPayload,
this.configure.env.get(
TokenConst.USER_REFRESH_TOKEN_EXPIRED,
TokenConst.DEFAULT_USER_REFRESH_TOKEN_EXPIRED,
),
);
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 = jwt.verify(
token.value,
this.configure.env.get(
TokenConst.USER_TOKEN_SECRET,
TokenConst.DEFAULT_USER_TOKEN_SECRET,
),
);
if (result) {
return token.user;
}
return null;
}
static JwtModuleFactory(configure: Configure) {
return JwtModule.registerAsync({
useFactory: async (): Promise<JwtModuleOptions> => {
const config = await configure.get<UserConfig>(
'user',
defaultUserConfig(configure),
);
const options: JwtModuleOptions = {
secret: configure.env.get(
TokenConst.USER_TOKEN_SECRET,
TokenConst.DEFAULT_USER_TOKEN_SECRET,
),
verifyOptions: {
ignoreExpiration: !configure.env.isProd(),
},
};
if (configure.env.isProd()) {
options.signOptions = { expiresIn: `${config.jwt.tokenExpired}s` };
}
return options;
},
});
}
}