Kurs NestJS · Moduł 4: Uwierzytelnianie
PROJEKT: System autentyfikacji dla legionu rzymskich kohort
W tej lekcji4
legacie bezpieczeństwa! Konsul Caesar.js powierza ci największe wyzwanie - stworzenie kompletnego systemu autentyfikacji dla całego legionu rzymskich kohort. To będzie prawdziwy Fort Knox Imperium, łączący wszystkie umiejętności, które poznałeś w poprzednich modułach!
Specyfikacja Projektu
Wymagania funkcjonalne
Twój system musi obsługiwać:
- Rejestracja i logowanie
- Email/hasło z walidacją siły hasła
- Weryfikacja email
- Social login (Google, GitHub, Facebook)
- Two-Factor Authentication (2FA)
- Zarządzanie sesjami
- JWT Access Tokens (15 min)
- Refresh Tokens (7 dni) z rotacją
- Multi-device support
- Session management
- Bezpieczeństwo
- Rate limiting
- Password hashing (bcrypt)
- Account lockout po nieudanych próbach
- Suspicious activity detection
- Administracja
- Role-based access control
- User management
- Audit logging
- Security monitoring
Implementacja
1. Główny kontroler autentyfikacji
1// auth.controller.ts
2@Controller('auth')
3@ApiTags('Authentication')
4export class AuthController {
5 constructor(
6 private authService: AuthService,
7 private passwordService: PasswordService,
8 private refreshTokenService: RefreshTokenService,
9 private twoFactorService: TwoFactorService,
10 private securityService: SecurityService,
11 ) {}
12
13 @Post('register')
14 @Throttle(3, 300) // 3 próby na 5 minut
15 @ApiOperation({ summary: 'Rejestracja nowego legionariusza' })
16 async register(
17 @Body() registerDto: RegisterDto,
18 @Headers('user-agent') userAgent: string,
19 @Ip() ipAddress: string,
20 ) {
21 // Sprawdź czy IP nie jest zablokowany
22 await this.securityService.checkIpBlacklist(ipAddress);
23
24 // Rejestracja
25 const user = await this.authService.register(registerDto);
26
27 // Logowanie bezpieczeństwa
28 await this.securityService.logSecurityEvent({
29 type: 'user_registered',
30 userId: user.id,
31 ipAddress,
32 userAgent,
33 details: { email: user.email },
34 });
35
36 return {
37 message: 'Legionariusz został zarejestrowany! Sprawdź email aby aktywować konto.',
38 user: {
39 id: user.id,
40 email: user.email,
41 emailVerified: user.emailVerified,
42 },
43 };
44 }
45
46 @Post('login')
47 @Throttle(5, 300) // 5 prób na 5 minut
48 @UseGuards(LocalAuthGuard)
49 @ApiOperation({ summary: 'Logowanie legionariusza' })
50 async login(
51 @Request() req,
52 @Body() loginDto: LoginDto,
53 @Headers('user-agent') userAgent: string,
54 @Ip() ipAddress: string,
55 ) {
56 const user = req.user;
57
58 // Sprawdź czy konto nie jest zablokowane
59 await this.authService.checkAccountLockout(user.id);
60
61 // Sprawdź czy wymagana jest 2FA
62 if (user.twoFactorEnabled) {
63 if (!loginDto.twoFactorCode) {
64 return {
65 requiresTwoFactor: true,
66 tempToken: await this.twoFactorService.generateTempToken(user.id),
67 };
68 }
69
70 const isValid2FA = await this.twoFactorService.verifyCode(
71 user.id,
72 loginDto.twoFactorCode,
73 );
74
75 if (!isValid2FA) {
76 await this.securityService.logSecurityEvent({
77 type: 'invalid_2fa_attempt',
78 userId: user.id,
79 ipAddress,
80 userAgent,
81 });
82
83 throw new UnauthorizedException('Nieprawidłowy kod 2FA!');
84 }
85 }
86
87 // Generuj tokeny
88 const tokens = await this.authService.generateAuthTokens(
89 user,
90 userAgent,
91 ipAddress,
92 );
93
94 // Aktualizuj dane logowania
95 await this.authService.updateLoginInfo(user.id, ipAddress);
96
97 return {
98 message: 'Witaj w legionie, legionisto!',
99 user: {
100 id: user.id,
101 email: user.email,
102 firstName: user.firstName,
103 lastName: user.lastName,
104 role: user.role,
105 emailVerified: user.emailVerified,
106 twoFactorEnabled: user.twoFactorEnabled,
107 },
108 tokens,
109 };
110 }
111
112 @Post('logout')
113 @UseGuards(JwtAuthGuard)
114 async logout(
115 @Request() req,
116 @Body() logoutDto: LogoutDto,
117 @Ip() ipAddress: string,
118 ) {
119 if (logoutDto.allDevices) {
120 await this.refreshTokenService.revokeAllUserTokens(
121 req.user.id,
122 'user_logout_all',
123 );
124 } else if (logoutDto.refreshToken) {
125 await this.refreshTokenService.revokeTokenByValue(
126 logoutDto.refreshToken,
127 'user_logout',
128 );
129 }
130
131 return {
132 message: logoutDto.allDevices
133 ? 'Wylogowano ze wszystkich urządzeń'
134 : 'Wylogowano pomyślnie',
135 };
136 }
137
138 @Post('forgot-password')
139 @Throttle(3, 3600) // 3 próby na godzinę
140 async forgotPassword(
141 @Body() forgotDto: ForgotPasswordDto,
142 @Ip() ipAddress: string,
143 ) {
144 await this.passwordService.sendPasswordResetEmail(
145 forgotDto.email,
146 ipAddress,
147 );
148
149 return {
150 message: 'Jeśli konto istnieje, instrukcje zostały wysłane na email.',
151 };
152 }
153}2. Serwis główny autentyfikacji
1// auth.service.ts
2@Injectable()
3export class AuthService {
4 constructor(
5 @InjectRepository(User) private usersRepository: Repository<User>,
6 private passwordService: PasswordService,
7 private refreshTokenService: RefreshTokenService,
8 private emailService: EmailService,
9 private jwtService: JwtService,
10 ) {}
11
12 async register(registerDto: RegisterDto): Promise<User> {
13 // Sprawdź czy user już istnieje
14 const existingUser = await this.usersRepository.findOne({
15 where: { email: registerDto.email },
16 });
17
18 if (existingUser) {
19 throw new ConflictException('Legionariusz z tym emailem już istnieje w legionie!');
20 }
21
22 // Hashuj hasło
23 const hashedPassword = await this.passwordService.hashPassword(
24 registerDto.password,
25 );
26
27 // Utwórz użytkownika
28 const user = this.usersRepository.create({
29 email: registerDto.email,
30 firstName: registerDto.firstName,
31 lastName: registerDto.lastName,
32 password: hashedPassword,
33 role: 'legionary', // Domyślna rola
34 emailVerified: false,
35 createdAt: new Date(),
36 });
37
38 const savedUser = await this.usersRepository.save(user);
39
40 // Wyślij email weryfikacyjny
41 await this.sendEmailVerification(savedUser);
42
43 return savedUser;
44 }
45
46 async validateUser(email: string, password: string): Promise<User | null> {
47 // Znajdź użytkownika
48 const user = await this.usersRepository.findOne({
49 where: { email },
50 select: ['id', 'email', 'password', 'firstName', 'lastName', 'role',
51 'emailVerified', 'twoFactorEnabled', 'accountLocked', 'lockoutUntil'],
52 });
53
54 if (!user) {
55 return null;
56 }
57
58 // Sprawdź blokadę konta
59 if (user.accountLocked && user.lockoutUntil && user.lockoutUntil > new Date()) {
60 throw new UnauthorizedException(
61 `Konto zablokowane do ${user.lockoutUntil.toLocaleString()}`
62 );
63 }
64
65 // Sprawdź hasło
66 const isPasswordValid = await this.passwordService.verifyPassword(
67 password,
68 user.password,
69 );
70
71 if (!isPasswordValid) {
72 await this.handleFailedLogin(user.id);
73 return null;
74 }
75
76 // Reset licznika nieudanych prób
77 await this.resetFailedLoginAttempts(user.id);
78
79 // Usuń hasło z obiektu
80 delete user.password;
81 return user;
82 }
83
84 async generateAuthTokens(
85 user: User,
86 userAgent: string,
87 ipAddress: string,
88 ): Promise<{ accessToken: string; refreshToken: string }> {
89 // Access Token
90 const accessTokenPayload = {
91 sub: user.id,
92 email: user.email,
93 role: user.role,
94 emailVerified: user.emailVerified,
95 };
96
97 const accessToken = this.jwtService.sign(accessTokenPayload, {
98 expiresIn: '15m',
99 });
100
101 // Refresh Token
102 const refreshToken = await this.refreshTokenService.generateRefreshToken(
103 user.id,
104 userAgent,
105 ipAddress,
106 );
107
108 return { accessToken, refreshToken };
109 }
110
111 private async handleFailedLogin(userId: number): Promise<void> {
112 const maxAttempts = 5;
113 const lockoutDuration = 30; // minut
114
115 // Pobierz liczbę nieudanych prób z ostatnich 15 minut
116 const since = new Date(Date.now() - 15 * 60 * 1000);
117 const failedAttempts = await this.getFailedAttemptsCount(userId, since);
118
119 if (failedAttempts >= maxAttempts) {
120 const lockoutUntil = new Date(Date.now() + lockoutDuration * 60 * 1000);
121
122 await this.usersRepository.update(userId, {
123 accountLocked: true,
124 lockoutUntil,
125 failedLoginAttempts: failedAttempts,
126 });
127
128 console.warn(`Konto ${userId} zostało zablokowane po ${failedAttempts} nieudanych próbach`);
129 }
130 }
131
132 private async resetFailedLoginAttempts(userId: number): Promise<void> {
133 await this.usersRepository.update(userId, {
134 accountLocked: false,
135 lockoutUntil: null,
136 failedLoginAttempts: 0,
137 });
138 }
139
140 async updateLoginInfo(userId: number, ipAddress: string): Promise<void> {
141 await this.usersRepository.update(userId, {
142 lastLoginAt: new Date(),
143 lastLoginIp: ipAddress,
144 });
145 }
146}Zadania do wykonania
Część 1: Podstawowa implementacja (4-6 godzin)
- Stwórz wszystkie entity z relacjami
- Zaimplementuj rejestrację z walidacją email
- Dodaj logowanie z JWT
- Zaimplementuj refresh tokeny
- Dodaj weryfikację email
Część 2: Bezpieczeństwo (3-4 godziny)
- Dodaj rate limiting
- Zaimplementuj account lockout
- Dodaj password reset
- Zaimplementuj monitoring bezpieczeństwa
- Dodaj audit logging
Część 3: Two-Factor Authentication (2-3 godziny)
- Zaimplementuj TOTP (Google Authenticator)
- Dodaj backup codes
- Stwórz QR codes dla konfiguracji
- Dodaj zarządzanie 2FA w panelu użytkownika
Część 4: Social Login (2-3 godziny)
- Zintegruj Google OAuth
- Dodaj GitHub OAuth
- Zaimplementuj łączenie kont społecznościowych
- Dodaj zarządzanie połączonymi kontami
Część 5: Administracja i monitoring (2-3 godziny)
- Stwórz panel administratora
- Dodaj zarządzanie użytkownikami
- Zaimplementuj raporty bezpieczeństwa
- Dodaj monitoring aktywności w czasie rzeczywistym
Część 6: Testy (2-3 godziny)
- Napisz testy jednostkowe dla wszystkich serwisów
- Dodaj testy integracyjne
- Stwórz testy e2e dla głównych flow
- Dodaj testy wydajnościowe
Kryteria oceny
Funkcjonalność - Wszystkie wymagania zaimplementowane poprawnie Bezpieczeństwo - Zastosowane najlepsze praktyki bezpieczeństwa Kod - Czytelny, dobrze zorganizowany kod zgodny z best practices Testy - Wysokie pokrycie testami, różne typy testów Dokumentacja - Kompletna dokumentacja API i instrukcje instalacji Performance - Optymalizacja zapytań i wydajności Monitoring - Logowanie i monitorowanie bezpieczeństwa
To największe wyzwanie w całym kursie NestJS! Powodzenia, legacie bezpieczeństwa. Twój legion liczy na nieprzenikalny system ochrony!
Kod do tej lekcji: src/auth/complete-auth-system.ts
1// PROJEKT: Kompletny System Authentication
2// Polacz wszystkie elementy w jeden dzialajacy system
3import { Module, Controller, Post, Get, Body, UseGuards, Request } from '@nestjs/common';
4import { Injectable, UnauthorizedException } from '@nestjs/common';
5import { JwtModule, JwtService } from '@nestjs/jwt';
6import { PassportModule } from '@nestjs/passport';
7
8// ============================================
9// 1. AuthService - serce systemu
10// ============================================
11@Injectable()
12class AuthService {
13 private users: any[] = [];
14
15 constructor(private jwtService: JwtService) {}
16
17 // TODO: Zaimplementuj rejestracje
18 async register(username: string, password: string) {
19 // TODO: Sprawdz czy user juz istnieje
20 // TODO: Zahashuj haslo (bcrypt)
21 // TODO: Zapisz usera
22 // TODO: Zwroc potwierdzenie
23 return { message: 'TODO: implementacja' };
24 }
25
26 // TODO: Zaimplementuj logowanie
27 async login(username: string, password: string) {
28 // TODO: Znajdz usera
29 // TODO: Sprawdz haslo
30 // TODO: Wygeneruj JWT token
31 // TODO: Zwroc { accessToken, user }
32 return { accessToken: '', user: {} };
33 }
34
35 // TODO: Zaimplementuj walidacje tokenu
36 async validateToken(payload: any) {
37 // TODO: Znajdz usera po payload.sub
38 // TODO: Zwroc usera lub null
39 return null;
40 }
41}
42
43// ============================================
44// 2. AuthController - endpointy
45// ============================================
46@Controller('auth')
47class AuthController {
48 constructor(private authService: AuthService) {}
49
50 @Post('register')
51 async register(@Body() body: { username: string; password: string }) {
52 return this.authService.register(body.username, body.password);
53 }
54
55 @Post('login')
56 async login(@Body() body: { username: string; password: string }) {
57 return this.authService.login(body.username, body.password);
58 }
59
60 // TODO: Dodaj @UseGuards(JwtAuthGuard)
61 @Get('profile')
62 getProfile(@Request() req: any) {
63 return { user: req.user };
64 }
65}
66
67// ============================================
68// 3. AuthModule - konfiguracja
69// ============================================
70@Module({
71 imports: [
72 PassportModule.register({ defaultStrategy: 'jwt' }),
73 JwtModule.register({
74 secret: 'spqr-secret-imperium',
75 signOptions: { expiresIn: '24h' },
76 }),
77 ],
78 providers: [AuthService],
79 controllers: [AuthController],
80 exports: [AuthService],
81})
82class AuthModule {}
83
84console.log('=== Kompletny System Authentication ===');
85console.log('1. Register: POST /auth/register');
86console.log('2. Login: POST /auth/login -> JWT token');
87console.log('3. Profile: GET /auth/profile (chroniony)');
88console.log('Polacz: AuthService + JwtStrategy + Guards');
89Widzisz błąd w tej lekcji?