Dyplomato Imperium! Architekt Vitruvius wprowadza cię w sztukę zawierania sojuszy z innymi prowincjami rzymskimi. W świecie cyfrowym to oznacza OAuth i Social Login - pozwalanie obywatelom z innych prowincji (Google, Facebook, GitHub) na bezpieczne wchodzenie do systemu twojego legionu!
OAuth to jak dyplomatyczne porozumienie między prowincjami Imperium - pozwala obywatelom z jednej prowincji (aplikacji) bezpiecznie korzystać z zasobów innej prowincji, bez ujawniania swoich sekretów (haseł).
Wyobraź sobie, że twój legion zawiera sojusz z prowincją Google. Gdy nowy obywatel chce dołączyć do twojego legionu, może udowodnić swoją tożsamość przez swoją macierzystą prowincję, zamiast tworzyć nowe konto w twoim obozie.
1// Instalacja pakietów
2// npm install @nestjs/passport passport passport-google-oauth20
3// npm install -D @types/passport-google-oauth20
4
5// google.strategy.ts
6import { Injectable } from '@nestjs/common';
7import { PassportStrategy } from '@nestjs/passport';
8import { Strategy, VerifyCallback } from 'passport-google-oauth20';
9import { ConfigService } from '@nestjs/config';
10
11@Injectable()
12export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
13 constructor(
14 private configService: ConfigService,
15 private usersService: UsersService,
16 ) {
17 super({
18 clientID: configService.get('GOOGLE_CLIENT_ID'),
19 clientSecret: configService.get('GOOGLE_CLIENT_SECRET'),
20 callbackURL: configService.get('GOOGLE_CALLBACK_URL'),
21 scope: ['email', 'profile'],
22 });
23 }
24
25 async validate(
26 accessToken: string,
27 refreshToken: string,
28 profile: any,
29 done: VerifyCallback,
30 ): Promise<any> {
31 const { name, emails, photos } = profile;
32
33 const citizenData = {
34 email: emails[0].value,
35 firstName: name.givenName,
36 lastName: name.familyName,
37 picture: photos[0].value,
38 accessToken,
39 refreshToken,
40 provider: 'google',
41 providerId: profile.id,
42 };
43
44 // Znajdź lub utwórz obywatela w bazie legionu
45 const citizen = await this.usersService.findOrCreateSocialUser(citizenData);
46
47 console.log('Obywatel z Google dołączył do legionu:', citizen.email);
48
49 done(null, citizen);
50 }
51}1// users.service.ts
2@Injectable()
3export class UsersService {
4 constructor(
5 @InjectRepository(User) private usersRepository: Repository<User>,
6 private jwtService: JwtService,
7 ) {}
8
9 async findOrCreateSocialUser(socialData: any): Promise<User> {
10 // Szukaj użytkownika po email lub provider ID
11 let user = await this.usersRepository.findOne({
12 where: [
13 { email: socialData.email },
14 {
15 provider: socialData.provider,
16 providerId: socialData.providerId
17 },
18 ],
19 });
20
21 if (user) {
22 // Aktualizuj dane z providera
23 user.firstName = socialData.firstName;
24 user.lastName = socialData.lastName;
25 user.picture = socialData.picture;
26 user.lastLoginAt = new Date();
27
28 await this.usersRepository.save(user);
29
30 console.log('Powrót znanego obywatela:', user.email);
31 } else {
32 // Utwórz nowego obywatela Imperium
33 user = this.usersRepository.create({
34 email: socialData.email,
35 firstName: socialData.firstName,
36 lastName: socialData.lastName,
37 picture: socialData.picture,
38 provider: socialData.provider,
39 providerId: socialData.providerId,
40 role: 'miles', // Domyślna ranga - żołnierz
41 emailVerified: true, // OAuth providers weryfikują email
42 createdAt: new Date(),
43 lastLoginAt: new Date(),
44 });
45
46 await this.usersRepository.save(user);
47
48 console.log('Nowy obywatel z', socialData.provider, ':', user.email);
49 }
50
51 return user;
52 }
53
54 async generateSocialLoginTokens(user: User) {
55 const payload = {
56 sub: user.id,
57 email: user.email,
58 role: user.role,
59 provider: user.provider,
60 };
61
62 const accessToken = this.jwtService.sign(payload, { expiresIn: '1h' });
63 const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });
64
65 // Zapisz refresh token w bazie
66 await this.usersRepository.update(user.id, {
67 refreshToken: await bcrypt.hash(refreshToken, 10),
68 lastLoginAt: new Date(),
69 });
70
71 return { accessToken, refreshToken };
72 }
73}1// auth.controller.ts
2@Controller('auth')
3export class AuthController {
4 constructor(
5 private authService: AuthService,
6 private usersService: UsersService,
7 ) {}
8
9 // Rozpoczęcie procesu logowania przez Google
10 @Get('google')
11 @UseGuards(AuthGuard('google'))
12 async googleAuth(@Req() req) {
13 // Passport automatycznie przekieruje do Google
14 }
15
16 // Callback po autoryzacji Google
17 @Get('google/callback')
18 @UseGuards(AuthGuard('google'))
19 async googleAuthRedirect(@Req() req, @Res() res) {
20 try {
21 const user = req.user;
22 const tokens = await this.usersService.generateSocialLoginTokens(user);
23
24 // Redirect do frontendu z tokenami
25 const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3000';
26 const redirectUrl = `${frontendUrl}/auth/callback?` +
27 `token=${tokens.accessToken}&` +
28 `refresh=${tokens.refreshToken}&` +
29 `user=${encodeURIComponent(JSON.stringify({
30 id: user.id,
31 email: user.email,
32 name: `${user.firstName} ${user.lastName}`,
33 picture: user.picture,
34 provider: user.provider
35 }))}`
36
37 res.redirect(redirectUrl);
38 } catch (error) {
39 console.error('❌ Błąd podczas OAuth callback:', error);
40 res.redirect('/auth/error?message=oauth_failed');
41 }
42 }
43
44 // Endpoint do logowania przez różnych providerów
45 @Post('social/login')
46 async socialLogin(@Body() socialLoginDto: SocialLoginDto) {
47 const user = await this.usersService.findOrCreateSocialUser(socialLoginDto);
48 const tokens = await this.usersService.generateSocialLoginTokens(user);
49
50 return {
51 message: 'Pomyślne logowanie przez sojusz Imperium!',
52 user: {
53 id: user.id,
54 email: user.email,
55 name: `${user.firstName} ${user.lastName}`,
56 picture: user.picture,
57 provider: user.provider,
58 role: user.role,
59 },
60 tokens,
61 };
62 }
63}1// facebook.strategy.ts
2import { Injectable } from '@nestjs/common';
3import { PassportStrategy } from '@nestjs/passport';
4import { Strategy, Profile } from 'passport-facebook';
5
6@Injectable()
7export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') {
8 constructor(
9 private configService: ConfigService,
10 private usersService: UsersService,
11 ) {
12 super({
13 clientID: configService.get('FACEBOOK_APP_ID'),
14 clientSecret: configService.get('FACEBOOK_APP_SECRET'),
15 callbackURL: configService.get('FACEBOOK_CALLBACK_URL'),
16 scope: 'email',
17 profileFields: ['emails', 'name', 'picture.type(large)'],
18 });
19 }
20
21 async validate(
22 accessToken: string,
23 refreshToken: string,
24 profile: Profile,
25 done: Function,
26 ): Promise<any> {
27 const { name, emails, photos } = profile;
28
29 const citizenData = {
30 email: emails[0].value,
31 firstName: name.givenName,
32 lastName: name.familyName,
33 picture: photos[0].value,
34 accessToken,
35 refreshToken,
36 provider: 'facebook',
37 providerId: profile.id,
38 };
39
40 const citizen = await this.usersService.findOrCreateSocialUser(citizenData);
41
42 console.log('Obywatel z Facebook dołączył do legionu:', citizen.email);
43
44 done(null, citizen);
45 }
46}1// github.strategy.ts
2import { Injectable } from '@nestjs/common';
3import { PassportStrategy } from '@nestjs/passport';
4import { Strategy, Profile } from 'passport-github2';
5
6@Injectable()
7export class GitHubStrategy extends PassportStrategy(Strategy, 'github') {
8 constructor(
9 private configService: ConfigService,
10 private usersService: UsersService,
11 ) {
12 super({
13 clientID: configService.get('GITHUB_CLIENT_ID'),
14 clientSecret: configService.get('GITHUB_CLIENT_SECRET'),
15 callbackURL: configService.get('GITHUB_CALLBACK_URL'),
16 scope: ['user:email'],
17 });
18 }
19
20 async validate(
21 accessToken: string,
22 refreshToken: string,
23 profile: Profile,
24 done: Function,
25 ): Promise<any> {
26 const { username, emails, photos, displayName } = profile;
27
28 const names = displayName?.split(' ') || [username];
29
30 const citizenData = {
31 email: emails[0].value,
32 firstName: names[0],
33 lastName: names.slice(1).join(' ') || '',
34 picture: photos[0].value,
35 username,
36 accessToken,
37 refreshToken,
38 provider: 'github',
39 providerId: profile.id,
40 };
41
42 const citizen = await this.usersService.findOrCreateSocialUser(citizenData);
43
44 console.log('Obywatel z GitHub dołączył do legionu:', citizen.email);
45
46 done(null, citizen);
47 }
48}OAuth to potężne narzędzie do budowania sojuszy międzyplatformowych! Dzięki niemu obywatele z różnych prowincji Imperium mogą łatwo dołączyć do twojego legionu, używając tożsamości z zaufanych sojuszników.
W następnym ćwiczeniu poznamy zaawansowane techniki hashowania haseł - sztukę tworzenia nierozłamywalnych kodów skarbca Imperium!