TASK-032: /register → /login redirect, register API/로직 제거 TASK-033: SprintSyncService(.plans/sprints/ 파싱), /api/projects/:id/sync-sprints, sync-all TASK-034: 프로젝트 목록 SPRINT SYNC 버튼 추가, Gitea sync 시 Sprint 자동 동기화 TASK-035: 아바타 onError 처리, 캐시 실패 시 삭제 로직 추가 테스트 23/23 pass, FE 16 routes build 성공
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
import { JwtService } from '@nestjs/jwt';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import * as bcrypt from 'bcrypt';
|
|
import { IsString, IsNotEmpty } from 'class-validator';
|
|
|
|
export class LoginDto {
|
|
@IsString() @IsNotEmpty()
|
|
username!: string;
|
|
|
|
@IsString() @IsNotEmpty()
|
|
password!: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class AuthService {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly jwt: JwtService,
|
|
private readonly config: ConfigService,
|
|
) {}
|
|
|
|
async login(dto: LoginDto) {
|
|
const user = await this.prisma.user.findUnique({ where: { username: dto.username } });
|
|
if (!user) throw new UnauthorizedException('Invalid credentials');
|
|
|
|
const ok = await bcrypt.compare(dto.password, user.password);
|
|
if (!ok) throw new UnauthorizedException('Invalid credentials');
|
|
|
|
return this.signTokens(user.id, user.username, user.role);
|
|
}
|
|
|
|
async refresh(refreshToken: string) {
|
|
try {
|
|
const payload = this.jwt.verify<{ sub: number; username: string; role: string }>(
|
|
refreshToken,
|
|
{ secret: this.config.get<string>('JWT_SECRET') + '_refresh' },
|
|
);
|
|
const user = await this.prisma.user.findUnique({ where: { id: payload.sub } });
|
|
if (!user) throw new UnauthorizedException();
|
|
return this.signTokens(user.id, user.username, user.role);
|
|
} catch {
|
|
throw new UnauthorizedException('Invalid refresh token');
|
|
}
|
|
}
|
|
|
|
async getMe(userId: number) {
|
|
const user = await this.prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: { id: true, username: true, role: true, createdAt: true },
|
|
});
|
|
if (!user) throw new UnauthorizedException();
|
|
return user;
|
|
}
|
|
|
|
private signTokens(userId: number, username: string, role: string) {
|
|
const secret = this.config.get<string>('JWT_SECRET');
|
|
if (!secret) throw new Error('JWT_SECRET is not configured');
|
|
const payload = { sub: userId, username, role };
|
|
|
|
const accessToken = this.jwt.sign(payload, { secret, expiresIn: '15m' });
|
|
const refreshToken = this.jwt.sign(payload, { secret: secret + '_refresh', expiresIn: '7d' });
|
|
|
|
return { accessToken, refreshToken, username, role };
|
|
}
|
|
}
|