mistrzu bram! Konsul Caesar.js ma dla Ciebie nowe wyzwanie. Nasz legion przyciąga uwagę różnych sojuszników i partnerów handlowych, którzy chcą łatwo logować się na nasze forty używając swoich własnych systemów. Czas nauczyć się Passport.js - uniwersalnego systemu kontroli dostępu!
Wyobraź sobie, że jesteś dyplomatycznym strażnikiem w największym forum Imperium. Codziennie przybywają poselstwa z różnych państw:
Passport.js to framework, który pozwala na:
1// auth/strategies/local.strategy.ts
2import { Injectable, UnauthorizedException } from '@nestjs/common';
3import { PassportStrategy } from '@nestjs/passport';
4import { Strategy } from 'passport-local';
5import { AuthService } from '../services/auth.service';
6
7@Injectable()
8export class LocalStrategy extends PassportStrategy(Strategy) {
9 constructor(private authService: AuthService) {
10 super({
11 usernameField: 'username', // lub 'email'
12 passwordField: 'password',
13 passReqToCallback: false, // czy przekazać request do validate
14 });
15 }
16
17 async validate(username: string, password: string): Promise<any> {
18 const user = await this.authService.validateUser(username, password);
19
20 if (!user) {
21 throw new UnauthorizedException('Nieprawidłowe dane legionariusza!');
22 }
23
24 return user;
25 }
26}1// auth/guards/local-auth.guard.ts
2import { Injectable } from '@nestjs/common';
3import { AuthGuard } from '@nestjs/passport';
4
5@Injectable()
6export class LocalAuthGuard extends AuthGuard('local') {
7 constructor() {
8 super();
9 }
10
11 handleRequest(err, user, info, context) {
12 if (err || !user) {
13 throw new UnauthorizedException('Nieprawidłowe dane logowania!');
14 }
15 return user;
16 }
17}1# Instalacja
2npm install passport-google-oauth20
3npm install --save-dev @types/passport-google-oauth201// auth/strategies/google.strategy.ts
2import { Injectable } from '@nestjs/common';
3import { PassportStrategy } from '@nestjs/passport';
4import { Strategy, VerifyCallback } from 'passport-google-oauth20';
5import { ConfigService } from '@nestjs/config';
6import { AuthService } from '../services/auth.service';
7
8@Injectable()
9export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
10 constructor(
11 private configService: ConfigService,
12 private authService: AuthService,
13 ) {
14 super({
15 clientID: configService.get<string>('GOOGLE_CLIENT_ID'),
16 clientSecret: configService.get<string>('GOOGLE_CLIENT_SECRET'),
17 callbackURL: configService.get<string>('GOOGLE_CALLBACK_URL'),
18 scope: ['email', 'profile'],
19 });
20 }
21
22 async validate(
23 accessToken: string,
24 refreshToken: string,
25 profile: any,
26 done: VerifyCallback,
27 ): Promise<any> {
28 const { name, emails, photos, id } = profile;
29
30 const user = {
31 googleId: id,
32 email: emails[0].value,
33 firstName: name.givenName,
34 lastName: name.familyName,
35 picture: photos[0].value,
36 accessToken,
37 refreshToken,
38 };
39
40 // Znajdź lub stwórz użytkownika
41 const existingUser = await this.authService.findOrCreateGoogleUser(user);
42
43 done(null, existingUser);
44 }
45}1// auth/guards/google-auth.guard.ts
2import { Injectable } from '@nestjs/common';
3import { AuthGuard } from '@nestjs/passport';
4
5@Injectable()
6export class GoogleAuthGuard extends AuthGuard('google') {
7 constructor() {
8 super({
9 accessType: 'offline',
10 prompt: 'consent',
11 });
12 }
13}1# Instalacja
2npm install passport-facebook
3npm install --save-dev @types/passport-facebook1// auth/strategies/facebook.strategy.ts
2import { Injectable } from '@nestjs/common';
3import { PassportStrategy } from '@nestjs/passport';
4import { Strategy, Profile } from 'passport-facebook';
5import { ConfigService } from '@nestjs/config';
6import { AuthService } from '../services/auth.service';
7
8@Injectable()
9export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') {
10 constructor(
11 private configService: ConfigService,
12 private authService: AuthService,
13 ) {
14 super({
15 clientID: configService.get<string>('FACEBOOK_APP_ID'),
16 clientSecret: configService.get<string>('FACEBOOK_APP_SECRET'),
17 callbackURL: configService.get<string>('FACEBOOK_CALLBACK_URL'),
18 profileFields: ['id', 'emails', 'name', 'photos'],
19 });
20 }
21
22 async validate(
23 accessToken: string,
24 refreshToken: string,
25 profile: Profile,
26 done: Function,
27 ): Promise<any> {
28 const { name, emails, photos, id } = profile;
29
30 const user = {
31 facebookId: id,
32 email: emails?.[0]?.value,
33 firstName: name?.givenName,
34 lastName: name?.familyName,
35 picture: photos?.[0]?.value,
36 accessToken,
37 refreshToken,
38 };
39
40 const existingUser = await this.authService.findOrCreateFacebookUser(user);
41 done(null, existingUser);
42 }
43}1npm install passport-github2
2npm install --save-dev @types/passport-github21// auth/strategies/github.strategy.ts
2import { Injectable } from '@nestjs/common';
3import { PassportStrategy } from '@nestjs/passport';
4import { Strategy, Profile } from 'passport-github2';
5import { ConfigService } from '@nestjs/config';
6import { AuthService } from '../services/auth.service';
7
8@Injectable()
9export class GitHubStrategy extends PassportStrategy(Strategy, 'github') {
10 constructor(
11 private configService: ConfigService,
12 private authService: AuthService,
13 ) {
14 super({
15 clientID: configService.get<string>('GITHUB_CLIENT_ID'),
16 clientSecret: configService.get<string>('GITHUB_CLIENT_SECRET'),
17 callbackURL: configService.get<string>('GITHUB_CALLBACK_URL'),
18 scope: ['user:email'],
19 });
20 }
21
22 async validate(
23 accessToken: string,
24 refreshToken: string,
25 profile: Profile,
26 done: Function,
27 ): Promise<any> {
28 const { username, displayName, emails, photos, id } = profile;
29
30 const user = {
31 githubId: id,
32 username: username,
33 displayName: displayName,
34 email: emails?.[0]?.value,
35 picture: photos?.[0]?.value,
36 accessToken,
37 refreshToken,
38 };
39
40 const existingUser = await this.authService.findOrCreateGitHubUser(user);
41 done(null, existingUser);
42 }
43}1// entities/user.entity.ts (aktualizacja)
2import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
3import { Exclude } from 'class-transformer';
4
5@Entity('users')
6export class User {
7 @PrimaryGeneratedColumn()
8 id: number;
9
10 @Column({ unique: true, nullable: true })
11 username: string;
12
13 @Column({ unique: true })
14 email: string;
15
16 @Column({ nullable: true })
17 @Exclude()
18 password: string; // Nullable dla OAuth users
19
20 @Column({ default: 'LEGION_MEMBER' })
21 role: string;
22
23 @Column({ nullable: true })
24 firstName: string;
25
26 @Column({ nullable: true })
27 lastName: string;
28
29 @Column({ nullable: true })
30 picture: string;
31
32 @Column({ nullable: true })
33 cohortName: string;
34
35 // OAuth provider IDs
36 @Column({ nullable: true, unique: true })
37 googleId: string;
38
39 @Column({ nullable: true, unique: true })
40 facebookId: string;
41
42 @Column({ nullable: true, unique: true })
43 githubId: string;
44
45 // OAuth tokens (zaszyfrowowane)
46 @Column({ type: 'text', nullable: true })
47 @Exclude()
48 googleAccessToken: string;
49
50 @Column({ type: 'text', nullable: true })
51 @Exclude()
52 facebookAccessToken: string;
53
54 @Column({ type: 'text', nullable: true })
55 @Exclude()
56 githubAccessToken: string;
57
58 @Column({ default: true })
59 isActive: boolean;
60
61 @Column({ default: false })
62 emailVerified: boolean;
63
64 @CreateDateColumn()
65 joinedAt: Date;
66
67 @UpdateDateColumn()
68 lastLoginAt: Date;
69
70 @Column({ nullable: true })
71 lastLoginIp: string;
72
73 @Column({ default: 0 })
74 failedLoginAttempts: number;
75
76 @Column({ nullable: true })
77 lockedUntil: Date;
78
79 @Column({ nullable: true })
80 lastPasswordChange: Date;
81
82 // Provider information
83 @Column({ type: 'simple-array', default: [] })
84 authProviders: string[]; // ['local', 'google', 'facebook', 'github']
85}1// auth/services/auth.service.ts (rozszerzenie)
2export class AuthService {
3 // ... poprzednie metody
4
5 async findOrCreateGoogleUser(googleUser: any): Promise<User> {
6 // Sprawdź czy użytkownik już istnieje po Google ID
7 let user = await this.userService.findByGoogleId(googleUser.googleId);
8
9 if (user) {
10 // Aktualizuj token
11 await this.userService.updateGoogleToken(user.id, googleUser.accessToken);
12 return user;
13 }
14
15 // Sprawdź czy istnieje użytkownik z tym emailem
16 user = await this.userService.findByEmail(googleUser.email);
17
18 if (user) {
19 // Połącz konta
20 await this.userService.linkGoogleAccount(user.id, googleUser);
21 return user;
22 }
23
24 // Stwórz nowego użytkownika
25 return await this.userService.createGoogleUser(googleUser);
26 }
27
28 async findOrCreateFacebookUser(facebookUser: any): Promise<User> {
29 let user = await this.userService.findByFacebookId(facebookUser.facebookId);
30
31 if (user) {
32 await this.userService.updateFacebookToken(user.id, facebookUser.accessToken);
33 return user;
34 }
35
36 user = await this.userService.findByEmail(facebookUser.email);
37
38 if (user) {
39 await this.userService.linkFacebookAccount(user.id, facebookUser);
40 return user;
41 }
42
43 return await this.userService.createFacebookUser(facebookUser);
44 }
45
46 async findOrCreateGitHubUser(githubUser: any): Promise<User> {
47 let user = await this.userService.findByGitHubId(githubUser.githubId);
48
49 if (user) {
50 await this.userService.updateGitHubToken(user.id, githubUser.accessToken);
51 return user;
52 }
53
54 user = await this.userService.findByEmail(githubUser.email);
55
56 if (user) {
57 await this.userService.linkGitHubAccount(user.id, githubUser);
58 return user;
59 }
60
61 return await this.userService.createGitHubUser(githubUser);
62 }
63
64 async unlinkProvider(userId: number, provider: string): Promise<void> {
65 const user = await this.userService.findById(userId);
66
67 if (!user) {
68 throw new NotFoundException('Użytkownik nie znaleziony');
69 }
70
71 // Sprawdź czy użytkownik ma alternatywną metodę logowania
72 const remainingProviders = user.authProviders.filter(p => p !== provider);
73
74 if (remainingProviders.length === 0 && !user.password) {
75 throw new BadRequestException('Nie możesz odłączyć ostatniej metody logowania!');
76 }
77
78 await this.userService.unlinkProvider(userId, provider);
79 }
80}1// auth/controllers/oauth.controller.ts
2import {
3 Controller,
4 Get,
5 Post,
6 UseGuards,
7 Request,
8 Response,
9 Query,
10 BadRequestException
11} from '@nestjs/common';
12import { AuthService } from '../services/auth.service';
13import { GoogleAuthGuard } from '../guards/google-auth.guard';
14import { FacebookAuthGuard } from '../guards/facebook-auth.guard';
15import { GitHubAuthGuard } from '../guards/github-auth.guard';
16import { JwtAuthGuard } from '../guards/jwt-auth.guard';
17import { CurrentUser } from '../decorators/current-user.decorator';
18import { Public } from '../decorators/public.decorator';
19
20@Controller('auth')
21export class OAuthController {
22 constructor(private authService: AuthService) {}
23
24 // Google OAuth
25 @Public()
26 @Get('google')
27 @UseGuards(GoogleAuthGuard)
28 async googleAuth(@Request() req) {
29 // Rozpocznie proces OAuth
30 }
31
32 @Public()
33 @Get('google/callback')
34 @UseGuards(GoogleAuthGuard)
35 async googleAuthRedirect(@Request() req, @Response() res) {
36 const user = req.user;
37 const tokens = await this.authService.generateTokens(user);
38
39 // Przekieruj z tokenem (w prawdziwej aplikacji użyj secure cookies)
40 const redirectUrl = `${process.env.FRONTEND_URL}/auth/success?token=${tokens.access_token}`1 res.redirect(redirectUrl);
2 }
3
4 // Facebook OAuth
5 @Public()
6 @Get('facebook')
7 @UseGuards(FacebookAuthGuard)
8 async facebookAuth(@Request() req) {
9 // Rozpocznie proces OAuth
10 }
11
12 @Public()
13 @Get('facebook/callback')
14 @UseGuards(FacebookAuthGuard)
15 async facebookAuthRedirect(@Request() req, @Response() res) {
16 const user = req.user;
17 const tokens = await this.authService.generateTokens(user);
18
19 const redirectUrl = `${process.env.FRONTEND_URL}/auth/success?token=${tokens.access_token}`;
20 res.redirect(redirectUrl);
21 }
22
23 // GitHub OAuth
24 @Public()
25 @Get('github')
26 @UseGuards(GitHubAuthGuard)
27 async githubAuth(@Request() req) {
28 // Rozpocznie proces OAuth
29 }
30
31 @Public()
32 @Get('github/callback')
33 @UseGuards(GitHubAuthGuard)
34 async githubAuthRedirect(@Request() req, @Response() res) {
35 const user = req.user;
36 const tokens = await this.authService.generateTokens(user);
37
38 const redirectUrl = `${process.env.FRONTEND_URL}/auth/success?token=${tokens.access_token}`;
39 res.redirect(redirectUrl);
40 }
41
42 // Link/Unlink accounts
43 @Post('link/google')
44 @UseGuards(JwtAuthGuard)
45 async linkGoogle(@CurrentUser() user: any, @Query('code') code: string) {
46 if (!code) {
47 throw new BadRequestException('Brak kodu autoryzacyjnego');
48 }
49
50 // Implementuj łączenie kont
51 return { message: 'Konto Google zostało połączone!' };
52 }
53
54 @Post('unlink/:provider')
55 @UseGuards(JwtAuthGuard)
56 async unlinkProvider(
57 @CurrentUser() user: any,
58 @Param('provider') provider: string,
59 ) {
60 await this.authService.unlinkProvider(user.id, provider);
61 return { message: `Konto ${provider} zostało odłączone!` };
62 }
63
64 @Get('providers')
65 @UseGuards(JwtAuthGuard)
66 async getLinkedProviders(@CurrentUser() user: any) {
67 const userDetails = await this.userService.findById(user.id);
68 return {
69 providers: userDetails.authProviders,
70 canUnlink: userDetails.authProviders.length > 1 || !!userDetails.password,
71 };
72 }
73}1// auth/strategies/api-key.strategy.ts
2import { Injectable, UnauthorizedException } from '@nestjs/common';
3import { PassportStrategy } from '@nestjs/passport';
4import { Strategy } from 'passport-custom';
5import { Request } from 'express';
6import { AuthService } from '../services/auth.service';
7
8@Injectable()
9export class ApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') {
10 constructor(private authService: AuthService) {
11 super();
12 }
13
14 async validate(req: Request): Promise<any> {
15 const apiKey = req.headers['x-api-key'] as string;
16
17 if (!apiKey) {
18 throw new UnauthorizedException('Brak klucza API!');
19 }
20
21 const user = await this.authService.validateApiKey(apiKey);
22
23 if (!user) {
24 throw new UnauthorizedException('Nieprawidłowy klucz API!');
25 }
26
27 return user;
28 }
29}1# .env
2# Google OAuth
3GOOGLE_CLIENT_ID=your-google-client-id
4GOOGLE_CLIENT_SECRET=your-google-client-secret
5GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback
6
7# Facebook OAuth
8FACEBOOK_APP_ID=your-facebook-app-id
9FACEBOOK_APP_SECRET=your-facebook-app-secret
10FACEBOOK_CALLBACK_URL=http://localhost:3000/auth/facebook/callback
11
12# GitHub OAuth
13GITHUB_CLIENT_ID=your-github-client-id
14GITHUB_CLIENT_SECRET=your-github-client-secret
15GITHUB_CALLBACK_URL=http://localhost:3000/auth/github/callback
16
17# Frontend URL
18FRONTEND_URL=http://localhost:30011// auth/strategies/session.strategy.ts
2import { Injectable } from '@nestjs/common';
3import { PassportStrategy } from '@nestjs/passport';
4import { Strategy } from 'passport-local';
5import { AuthService } from '../services/auth.service';
6
7@Injectable()
8export class SessionStrategy extends PassportStrategy(Strategy, 'session') {
9 constructor(private authService: AuthService) {
10 super({
11 usernameField: 'username',
12 passwordField: 'password',
13 });
14 }
15
16 async validate(username: string, password: string): Promise<any> {
17 const user = await this.authService.validateUser(username, password);
18 return user;
19 }
20}
21
22// Session serialization
23export class SessionSerializer {
24 serializeUser(user: any, done: Function) {
25 done(null, user.id);
26 }
27
28 async deserializeUser(userId: number, done: Function) {
29 const user = await this.userService.findById(userId);
30 done(null, user);
31 }
32}1// auth.module.ts (pełna wersja)
2import { Module, MiddlewareConsumer } from '@nestjs/common';
3import { JwtModule } from '@nestjs/jwt';
4import { PassportModule } from '@nestjs/passport';
5import { TypeOrmModule } from '@nestjs/typeorm';
6import { ConfigModule } from '@nestjs/config';
7
8import { User } from './entities/user.entity';
9import { AuthService } from './services/auth.service';
10import { UserService } from './services/user.service';
11import { CryptoService } from './services/crypto.service';
12
13import { AuthController } from './controllers/auth.controller';
14import { OAuthController } from './controllers/oauth.controller';
15
16import { LocalStrategy } from './strategies/local.strategy';
17import { JwtStrategy } from './strategies/jwt.strategy';
18import { GoogleStrategy } from './strategies/google.strategy';
19import { FacebookStrategy } from './strategies/facebook.strategy';
20import { GitHubStrategy } from './strategies/github.strategy';
21import { ApiKeyStrategy } from './strategies/api-key.strategy';
22
23import { LocalAuthGuard } from './guards/local-auth.guard';
24import { JwtAuthGuard } from './guards/jwt-auth.guard';
25import { GoogleAuthGuard } from './guards/google-auth.guard';
26import { FacebookAuthGuard } from './guards/facebook-auth.guard';
27import { GitHubAuthGuard } from './guards/github-auth.guard';
28
29import { jwtConfig } from './jwt.config';
30
31@Module({
32 imports: [
33 TypeOrmModule.forFeature([User]),
34 PassportModule.register({ defaultStrategy: 'jwt' }),
35 JwtModule.registerAsync(jwtConfig),
36 ConfigModule,
37 ],
38 controllers: [AuthController, OAuthController],
39 providers: [
40 // Services
41 AuthService,
42 UserService,
43 CryptoService,
44
45 // Strategies
46 LocalStrategy,
47 JwtStrategy,
48 GoogleStrategy,
49 FacebookStrategy,
50 GitHubStrategy,
51 ApiKeyStrategy,
52
53 // Guards
54 LocalAuthGuard,
55 JwtAuthGuard,
56 GoogleAuthGuard,
57 FacebookAuthGuard,
58 GitHubAuthGuard,
59 ],
60 exports: [
61 AuthService,
62 UserService,
63 JwtAuthGuard,
64 LocalAuthGuard,
65 ],
66})
67export class AuthModule {}Zaimplementuj dodatkowe strategie:
1// Twój kod tutaj
2// 1. Discord OAuth Strategy
3// 2. Twitter OAuth Strategy
4// 3. LinkedIn OAuth Strategy
5// 4. Microsoft OAuth Strategy
6// 5. Custom LDAP Strategy dla firmowych kontGratulacje! Zostałeś mianowany głównym dyplomatycznym strażnikiem! Teraz umiesz:
✓ Konfigurować Passport.js z różnymi strategiami ✓ Implementować OAuth z Google, Facebook, GitHub ✓ Łączyć konta z różnych providerów ✓ Tworzyć custom strategie dla specjalnych przypadków ✓ Zarządzać wieloma metodami authentication ✓ Obsługiwać redirecty i callbacks OAuth
Teraz Twój legion może przyjmować gości z całego świata!