This repository has been archived by the owner on May 11, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #184 from igorkamyshev/social-login
Social login
- Loading branch information
Showing
30 changed files
with
564 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
ALTER TABLE public."user" | ||
ADD "googleId" VARCHAR(255) DEFAULT NULL, | ||
ADD "email" VARCHAR(255) DEFAULT NULL; | ||
|
||
UPDATE public."user" SET email=login WHERE email IS NULL; | ||
|
||
#DOWN | ||
|
||
ALTER TABLE public."user" | ||
DROP COLUMN "googleId", | ||
DROP COLUMN "email"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
|
||
import { EntitySaver } from '&back/db/EntitySaver'; | ||
import { GoogleProfile } from '&shared/models/user/external/GoogleProfile'; | ||
|
||
import { User } from '../domain/User.entity'; | ||
import { UserRepository } from '../domain/UserRepository'; | ||
import { PasswordEncoder } from '../infrastructure/PasswordEncoder/PasswordEncoder'; | ||
import { InvalidCredentialsException } from './exception/InvalidCredentialsException'; | ||
import { InvalidSocialRequestException } from './exception/InvalidSocialRequestException'; | ||
import { GoogleValidator } from './social/GoogleValidator'; | ||
|
||
@Injectable() | ||
export class SignInProvider { | ||
constructor( | ||
private readonly userRepo: UserRepository, | ||
private readonly passwordEncoder: PasswordEncoder, | ||
private readonly googleValidator: GoogleValidator, | ||
private readonly entitySaver: EntitySaver, | ||
) {} | ||
|
||
async signInByLogin(login: string, password: string): Promise<User> { | ||
const user = await this.userRepo.getOne(login); | ||
|
||
const passwordValid = await user.isPasswordValid( | ||
password, | ||
this.passwordEncoder, | ||
); | ||
|
||
if (!passwordValid) { | ||
throw new InvalidCredentialsException(login, password); | ||
} | ||
|
||
return user; | ||
} | ||
|
||
async signInByGoogle(profile: GoogleProfile): Promise<User> { | ||
const [valid, optionalUser] = await Promise.all([ | ||
this.googleValidator.isValid(profile), | ||
this.userRepo.findOneByGoogle(profile.id), | ||
]); | ||
|
||
if (!valid) { | ||
throw new InvalidSocialRequestException(profile.email, 'Google', profile); | ||
} | ||
|
||
// okay, user already exist, just sign-in | ||
if (optionalUser.nonEmpty()) { | ||
return optionalUser.get(); | ||
} | ||
|
||
const user = new User(`google-${profile.id}`); | ||
user.attachGoogle(profile.id); | ||
|
||
await this.entitySaver.save(user); | ||
|
||
return user; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
|
||
import { EntitySaver } from '&back/db/EntitySaver'; | ||
import { GoogleProfile } from '&shared/models/user/external/GoogleProfile'; | ||
|
||
import { UserRepository } from '../domain/UserRepository'; | ||
import { InvalidSocialRequestException } from './exception/InvalidSocialRequestException'; | ||
import { LoginAlreadyTakenException } from './exception/LoginAlreadyTakenException'; | ||
import { GoogleValidator } from './social/GoogleValidator'; | ||
|
||
@Injectable() | ||
export class SocialBinder { | ||
constructor( | ||
private readonly googleValidator: GoogleValidator, | ||
private readonly userRepo: UserRepository, | ||
private readonly entitySaver: EntitySaver, | ||
) {} | ||
|
||
async bindGoogle(login: string, profile: GoogleProfile) { | ||
const [valid, user] = await Promise.all([ | ||
this.googleValidator.isValid(profile), | ||
this.userRepo.getOne(login), | ||
]); | ||
|
||
if (!valid) { | ||
throw new InvalidSocialRequestException(login, 'Google', profile); | ||
} | ||
|
||
user.attachGoogle(profile.id); | ||
|
||
await this.entitySaver.save(user); | ||
} | ||
|
||
async bindTelegram(login: string, telegramId: number) { | ||
const attachedUser = await this.userRepo.findOneByTelegram(telegramId); | ||
|
||
if (attachedUser.nonEmpty()) { | ||
throw new LoginAlreadyTakenException(login); | ||
} | ||
|
||
const user = await this.userRepo.getOne(login); | ||
|
||
user.attachTelegram(telegramId); | ||
|
||
await this.entitySaver.save(user); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
back/src/user/application/exception/InvalidSocialRequestException.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export class InvalidSocialRequestException extends Error { | ||
public constructor( | ||
public readonly login: string, | ||
public readonly social: string, | ||
public readonly payload: any, | ||
) { | ||
super(`Invalid credentials for ${login} from ${social}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import * as deepEqual from 'fast-deep-equal'; | ||
import { OAuth2Client } from 'google-auth-library'; | ||
|
||
import { Configuration } from '&back/config/Configuration'; | ||
import { GoogleProfile } from '&shared/models/user/external/GoogleProfile'; | ||
|
||
@Injectable() | ||
export class GoogleValidator { | ||
private readonly client: OAuth2Client; | ||
private readonly googleClientId: string; | ||
|
||
constructor(config: Configuration) { | ||
this.googleClientId = config.getStringOrThrow('GOOGLE_CLIENT_ID'); | ||
|
||
const googleClientSecret = config.getStringOrThrow('GOOGLE_CLIENT_SECRET'); | ||
|
||
this.client = new OAuth2Client(this.googleClientId, googleClientSecret); | ||
} | ||
|
||
async isValid(profile: GoogleProfile): Promise<boolean> { | ||
const { token } = profile; | ||
|
||
try { | ||
const ticket = await this.client.verifyIdToken({ | ||
idToken: profile.token, | ||
audience: this.googleClientId, | ||
}); | ||
|
||
const payload = ticket.getPayload(); | ||
|
||
const payloadProfile: GoogleProfile = { | ||
token, | ||
name: payload.name, | ||
id: payload.sub, | ||
photo: payload.picture, | ||
email: payload.email, | ||
}; | ||
|
||
return deepEqual(profile, payloadProfile); | ||
} catch (error) { | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.