init studio admin backend
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { randomBytes, scryptSync, timingSafeEqual, createHash } from 'node:crypto';
|
||||
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { AdminLoginDto } from './dto';
|
||||
|
||||
@Injectable()
|
||||
export class AdminAuthService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async login(
|
||||
body: AdminLoginDto,
|
||||
context: { ipAddress?: string; userAgent?: string },
|
||||
) {
|
||||
const admin = await this.prisma.admin.findUnique({
|
||||
where: { email: body.email.trim().toLowerCase() },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
role: true,
|
||||
isActive: true,
|
||||
passwordHash: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!admin || !admin.isActive || !verifyPassword(body.password, admin.passwordHash)) {
|
||||
throw new UnauthorizedException('이메일 또는 비밀번호가 올바르지 않습니다.');
|
||||
}
|
||||
|
||||
const token = randomBytes(32).toString('hex');
|
||||
const tokenHash = hashToken(token);
|
||||
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 12);
|
||||
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.admin.update({
|
||||
where: { id: admin.id },
|
||||
data: { lastLoginAt: new Date() },
|
||||
}),
|
||||
this.prisma.adminSession.create({
|
||||
data: {
|
||||
adminId: admin.id,
|
||||
tokenHash,
|
||||
ipAddress: context.ipAddress,
|
||||
userAgent: context.userAgent,
|
||||
expiresAt,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
token,
|
||||
expiresAt,
|
||||
admin: {
|
||||
id: admin.id,
|
||||
email: admin.email,
|
||||
name: admin.name,
|
||||
role: admin.role,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function verifyPassword(password: string, passwordHash: string) {
|
||||
const [scheme, salt, storedHash] = passwordHash.split(':');
|
||||
if (scheme !== 'scrypt' || !salt || !storedHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentHash = scryptSync(password, salt, 64);
|
||||
const storedBuffer = Buffer.from(storedHash, 'hex');
|
||||
|
||||
if (storedBuffer.length !== currentHash.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return timingSafeEqual(storedBuffer, currentHash);
|
||||
}
|
||||
|
||||
function hashToken(token: string) {
|
||||
return createHash('sha256').update(token).digest('hex');
|
||||
}
|
||||
Reference in New Issue
Block a user