training_data/apps/server/src/auth/auth.service.ts

188 lines
5.5 KiB
TypeScript
Raw Normal View History

2024-09-03 20:19:33 +08:00
import {
2024-09-09 18:48:07 +08:00
Injectable,
UnauthorizedException,
BadRequestException,
2024-12-30 08:26:40 +08:00
Logger,
InternalServerErrorException,
2024-09-03 20:19:33 +08:00
} from '@nestjs/common';
2024-12-30 08:26:40 +08:00
import { StaffService } from '../models/staff/staff.service';
import {
db,
AuthSchema,
JwtPayload,
} from '@nicestack/common';
import * as argon2 from 'argon2';
2024-09-03 20:19:33 +08:00
import { JwtService } from '@nestjs/jwt';
2024-12-30 08:26:40 +08:00
import { redis } from '@server/utils/redis/redis.service';
import { UserProfileService } from './utils';
import { SessionInfo, SessionService } from './session.service';
import { tokenConfig } from './config';
import { z } from 'zod';
2024-09-03 20:19:33 +08:00
@Injectable()
export class AuthService {
2024-12-30 08:26:40 +08:00
private logger = new Logger(AuthService.name)
2024-09-09 18:48:07 +08:00
constructor(
private readonly staffService: StaffService,
2024-12-30 08:26:40 +08:00
private readonly jwtService: JwtService,
private readonly sessionService: SessionService,
2024-09-09 18:48:07 +08:00
) { }
2024-12-30 08:26:40 +08:00
private async generateTokens(payload: JwtPayload): Promise<{
accessToken: string;
refreshToken: string;
}> {
const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(payload, {
expiresIn: `${tokenConfig.accessToken.expirationMs / 1000}s`,
}),
this.jwtService.signAsync(
{ sub: payload.sub },
{ expiresIn: `${tokenConfig.refreshToken.expirationMs / 1000}s` },
),
]);
return { accessToken, refreshToken };
}
async signIn(data: z.infer<typeof AuthSchema.signInRequset>): Promise<SessionInfo> {
2024-09-10 10:31:24 +08:00
const { username, password, phoneNumber } = data;
2024-12-30 08:26:40 +08:00
let staff = await db.staff.findFirst({
where: { OR: [{ username }, { phoneNumber }], deletedAt: null },
2024-09-10 10:31:24 +08:00
});
2024-12-30 08:26:40 +08:00
if (!staff && phoneNumber) {
staff = await this.signUp({
showname: '新用户',
username: phoneNumber,
phoneNumber,
password: phoneNumber,
});
} else if (!staff) {
throw new UnauthorizedException('帐号不存在');
2024-09-03 20:19:33 +08:00
}
2024-12-30 08:26:40 +08:00
if (!staff.enabled) {
throw new UnauthorizedException('帐号已禁用');
}
const isPasswordMatch = phoneNumber || await argon2.verify(staff.password, password);
2024-09-09 18:48:07 +08:00
if (!isPasswordMatch) {
2024-12-30 08:26:40 +08:00
throw new UnauthorizedException('帐号或密码错误');
2024-09-03 20:19:33 +08:00
}
2024-12-30 08:26:40 +08:00
try {
const payload = { sub: staff.id, username: staff.username };
const { accessToken, refreshToken } = await this.generateTokens(payload);
return await this.sessionService.createSession(
staff.id,
accessToken,
refreshToken,
{
accessTokenExpirationMs: tokenConfig.accessToken.expirationMs,
refreshTokenExpirationMs: tokenConfig.refreshToken.expirationMs,
sessionTTL: tokenConfig.accessToken.expirationTTL,
},
);
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException('创建会话失败');
}
}
async signUp(data: z.infer<typeof AuthSchema.signUpRequest>) {
const { username, phoneNumber, officerId } = data;
2024-09-09 18:48:07 +08:00
2024-12-30 08:26:40 +08:00
const existingUser = await db.staff.findFirst({
where: {
OR: [{ username }, { officerId }, { phoneNumber }],
deletedAt: null
2024-09-09 18:48:07 +08:00
},
});
2024-12-30 08:26:40 +08:00
if (existingUser) {
throw new BadRequestException('帐号或证件号已存在');
}
2024-09-09 18:48:07 +08:00
2024-12-30 08:26:40 +08:00
return this.staffService.create({
data: {
...data,
domainId: data.deptId,
}
});
2024-09-09 18:48:07 +08:00
}
async refreshToken(data: z.infer<typeof AuthSchema.refreshTokenRequest>) {
2024-12-30 08:26:40 +08:00
const { refreshToken, sessionId } = data;
2024-09-09 18:48:07 +08:00
let payload: JwtPayload;
try {
payload = this.jwtService.verify(refreshToken);
2024-12-30 08:26:40 +08:00
} catch {
throw new UnauthorizedException('用户会话已过期');
2024-09-03 20:19:33 +08:00
}
2024-12-30 08:26:40 +08:00
const session = await this.sessionService.getSession(payload.sub, sessionId);
if (!session || session.refresh_token !== refreshToken) {
throw new UnauthorizedException('用户会话已过期');
2024-09-09 18:48:07 +08:00
}
2024-12-30 08:26:40 +08:00
const user = await db.staff.findUnique({ where: { id: payload.sub, deletedAt: null } });
2024-09-09 18:48:07 +08:00
if (!user) {
2024-12-30 08:26:40 +08:00
throw new UnauthorizedException('用户不存在');
2024-09-09 18:48:07 +08:00
}
2024-12-30 08:26:40 +08:00
const { accessToken } = await this.generateTokens({
sub: user.id,
username: user.username,
});
2024-09-03 20:19:33 +08:00
2024-12-30 08:26:40 +08:00
const updatedSession = {
...session,
access_token: accessToken,
access_token_expires_at: Date.now() + tokenConfig.accessToken.expirationMs,
};
await this.sessionService.saveSession(
payload.sub,
updatedSession,
tokenConfig.accessToken.expirationTTL,
);
await redis.del(UserProfileService.instance.getProfileCacheKey(payload.sub));
2024-09-09 18:48:07 +08:00
return {
2024-12-30 08:26:40 +08:00
access_token: accessToken,
access_token_expires_at: updatedSession.access_token_expires_at,
2024-09-09 18:48:07 +08:00
};
}
2024-12-30 08:26:40 +08:00
async changePassword(data: z.infer<typeof AuthSchema.changePassword>) {
const { newPassword, phoneNumber, username } = data;
const user = await db.staff.findFirst({
where: { OR: [{ username }, { phoneNumber }], deletedAt: null },
});
2024-09-03 20:19:33 +08:00
2024-12-30 08:26:40 +08:00
if (!user) {
throw new UnauthorizedException('用户不存在');
2024-09-03 20:19:33 +08:00
}
2024-12-30 08:26:40 +08:00
await this.staffService.update({
where: { id: user?.id },
data: {
password: newPassword,
2024-09-10 10:31:24 +08:00
}
2024-09-09 18:48:07 +08:00
});
2024-12-30 08:26:40 +08:00
return { message: '密码已修改' };
2024-09-09 18:48:07 +08:00
}
async logout(data: z.infer<typeof AuthSchema.logoutRequest>) {
2024-12-30 08:26:40 +08:00
const { refreshToken, sessionId } = data;
2024-09-03 20:19:33 +08:00
2024-12-30 08:26:40 +08:00
try {
const payload = this.jwtService.verify(refreshToken);
await Promise.all([
this.sessionService.deleteSession(payload.sub, sessionId),
redis.del(UserProfileService.instance.getProfileCacheKey(payload.sub)),
]);
} catch {
throw new UnauthorizedException('无效的会话');
2024-09-03 20:19:33 +08:00
}
2024-09-09 18:48:07 +08:00
2024-12-30 08:26:40 +08:00
return { message: '注销成功' };
2024-09-09 18:48:07 +08:00
}
2024-12-30 08:26:40 +08:00
}