refactor(backend): enhance auth strategies with type safety and better error handling (#5066)
Co-authored-by: mirarifhasan <arif.ishan05@gmail.com>
This commit is contained in:
parent
19362a4291
commit
08e5fa974c
6 changed files with 74 additions and 41 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { Strategy } from 'passport-github2';
|
||||
import { Strategy, Profile } from 'passport-github2';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthService } from '../auth.service';
|
||||
|
|
@ -6,6 +6,8 @@ import { UserService } from 'src/user/user.service';
|
|||
import * as O from 'fp-ts/Option';
|
||||
import * as E from 'fp-ts/Either';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { validateEmail } from 'src/utils';
|
||||
import { AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH } from 'src/errors';
|
||||
|
||||
@Injectable()
|
||||
export class GithubStrategy extends PassportStrategy(Strategy) {
|
||||
|
|
@ -15,18 +17,26 @@ export class GithubStrategy extends PassportStrategy(Strategy) {
|
|||
private configService: ConfigService,
|
||||
) {
|
||||
super({
|
||||
clientID: configService.get('INFRA.GITHUB_CLIENT_ID'),
|
||||
clientSecret: configService.get('INFRA.GITHUB_CLIENT_SECRET'),
|
||||
callbackURL: configService.get('INFRA.GITHUB_CALLBACK_URL'),
|
||||
scope: [configService.get('INFRA.GITHUB_SCOPE')],
|
||||
clientID: configService.get<string>('INFRA.GITHUB_CLIENT_ID'),
|
||||
clientSecret: configService.get<string>('INFRA.GITHUB_CLIENT_SECRET'),
|
||||
callbackURL: configService.get<string>('INFRA.GITHUB_CALLBACK_URL'),
|
||||
scope: [configService.get<string>('INFRA.GITHUB_SCOPE')],
|
||||
store: true,
|
||||
});
|
||||
}
|
||||
|
||||
async validate(accessToken, refreshToken, profile, done) {
|
||||
const user = await this.usersService.findUserByEmail(
|
||||
profile.emails[0].value,
|
||||
);
|
||||
async validate(
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile: Profile,
|
||||
done,
|
||||
) {
|
||||
const email = profile.emails?.[0].value;
|
||||
|
||||
if (!validateEmail(email))
|
||||
throw new UnauthorizedException(AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH);
|
||||
|
||||
const user = await this.usersService.findUserByEmail(email);
|
||||
|
||||
if (O.isNone(user)) {
|
||||
const createdUser = await this.usersService.createUserSSO(
|
||||
|
|
@ -38,7 +48,7 @@ export class GithubStrategy extends PassportStrategy(Strategy) {
|
|||
}
|
||||
|
||||
/**
|
||||
* * displayName and photoURL maybe null if user logged-in via magic-link before SSO
|
||||
* displayName and photoURL maybe null if user logged-in via magic-link before SSO
|
||||
*/
|
||||
if (!user.value.displayName || !user.value.photoURL) {
|
||||
const updatedUser = await this.usersService.updateUserDetails(
|
||||
|
|
@ -51,8 +61,8 @@ export class GithubStrategy extends PassportStrategy(Strategy) {
|
|||
}
|
||||
|
||||
/**
|
||||
* * Check to see if entry for Github is present in the Account table for user
|
||||
* * If user was created with another provider findUserByEmail may return true
|
||||
* Check to see if entry for Github is present in the Account table for user
|
||||
* If user was created with another provider findUserByEmail may return true
|
||||
*/
|
||||
const providerAccountExists =
|
||||
await this.authService.checkIfProviderAccountExists(user.value, profile);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
|
||||
import { Strategy, VerifyCallback, Profile } from 'passport-google-oauth20';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
|
|
@ -6,6 +6,9 @@ import * as O from 'fp-ts/Option';
|
|||
import { AuthService } from '../auth.service';
|
||||
import * as E from 'fp-ts/Either';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Request } from 'express';
|
||||
import { validateEmail } from 'src/utils';
|
||||
import { AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH } from 'src/errors';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleStrategy extends PassportStrategy(Strategy) {
|
||||
|
|
@ -15,10 +18,10 @@ export class GoogleStrategy extends PassportStrategy(Strategy) {
|
|||
private configService: ConfigService,
|
||||
) {
|
||||
super({
|
||||
clientID: configService.get('INFRA.GOOGLE_CLIENT_ID'),
|
||||
clientSecret: configService.get('INFRA.GOOGLE_CLIENT_SECRET'),
|
||||
callbackURL: configService.get('INFRA.GOOGLE_CALLBACK_URL'),
|
||||
scope: configService.get('INFRA.GOOGLE_SCOPE').split(','),
|
||||
clientID: configService.get<string>('INFRA.GOOGLE_CLIENT_ID'),
|
||||
clientSecret: configService.get<string>('INFRA.GOOGLE_CLIENT_SECRET'),
|
||||
callbackURL: configService.get<string>('INFRA.GOOGLE_CALLBACK_URL'),
|
||||
scope: configService.get<string>('INFRA.GOOGLE_SCOPE').split(','),
|
||||
passReqToCallback: true,
|
||||
store: true,
|
||||
});
|
||||
|
|
@ -26,14 +29,17 @@ export class GoogleStrategy extends PassportStrategy(Strategy) {
|
|||
|
||||
async validate(
|
||||
req: Request,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
profile,
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile: Profile,
|
||||
done: VerifyCallback,
|
||||
) {
|
||||
const user = await this.usersService.findUserByEmail(
|
||||
profile.emails[0].value,
|
||||
);
|
||||
const email = profile.emails?.[0].value;
|
||||
|
||||
if (!validateEmail(email))
|
||||
throw new UnauthorizedException(AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH);
|
||||
|
||||
const user = await this.usersService.findUserByEmail(email);
|
||||
|
||||
if (O.isNone(user)) {
|
||||
const createdUser = await this.usersService.createUserSSO(
|
||||
|
|
@ -45,7 +51,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy) {
|
|||
}
|
||||
|
||||
/**
|
||||
* * displayName and photoURL maybe null if user logged-in via magic-link before SSO
|
||||
* displayName and photoURL maybe null if user logged-in via magic-link before SSO
|
||||
*/
|
||||
if (!user.value.displayName || !user.value.photoURL) {
|
||||
const updatedUser = await this.usersService.updateUserDetails(
|
||||
|
|
@ -58,8 +64,8 @@ export class GoogleStrategy extends PassportStrategy(Strategy) {
|
|||
}
|
||||
|
||||
/**
|
||||
* * Check to see if entry for Google is present in the Account table for user
|
||||
* * If user was created with another provider findUserByEmail may return true
|
||||
* Check to see if entry for Google is present in the Account table for user
|
||||
* If user was created with another provider findUserByEmail may return true
|
||||
*/
|
||||
const providerAccountExists =
|
||||
await this.authService.checkIfProviderAccountExists(user.value, profile);
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|||
),
|
||||
),
|
||||
]),
|
||||
secretOrKey: configService.get('JWT_SECRET'),
|
||||
secretOrKey: configService.get<string>('JWT_SECRET'),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import { UserService } from 'src/user/user.service';
|
|||
import * as O from 'fp-ts/Option';
|
||||
import * as E from 'fp-ts/Either';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { validateEmail } from 'src/utils';
|
||||
import { AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH } from 'src/errors';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftStrategy extends PassportStrategy(Strategy) {
|
||||
|
|
@ -15,19 +17,27 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
|
|||
private configService: ConfigService,
|
||||
) {
|
||||
super({
|
||||
clientID: configService.get('INFRA.MICROSOFT_CLIENT_ID'),
|
||||
clientSecret: configService.get('INFRA.MICROSOFT_CLIENT_SECRET'),
|
||||
callbackURL: configService.get('INFRA.MICROSOFT_CALLBACK_URL'),
|
||||
scope: configService.get('INFRA.MICROSOFT_SCOPE').split(','),
|
||||
tenant: configService.get('INFRA.MICROSOFT_TENANT'),
|
||||
clientID: configService.get<string>('INFRA.MICROSOFT_CLIENT_ID'),
|
||||
clientSecret: configService.get<string>('INFRA.MICROSOFT_CLIENT_SECRET'),
|
||||
callbackURL: configService.get<string>('INFRA.MICROSOFT_CALLBACK_URL'),
|
||||
scope: configService.get<string>('INFRA.MICROSOFT_SCOPE').split(','),
|
||||
tenant: configService.get<string>('INFRA.MICROSOFT_TENANT'),
|
||||
store: true,
|
||||
});
|
||||
}
|
||||
|
||||
async validate(accessToken: string, refreshToken: string, profile, done) {
|
||||
const user = await this.usersService.findUserByEmail(
|
||||
profile.emails[0].value,
|
||||
);
|
||||
async validate(
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile,
|
||||
done,
|
||||
) {
|
||||
const email = profile?.emails?.[0]?.value;
|
||||
|
||||
if (!validateEmail(email))
|
||||
throw new UnauthorizedException(AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH);
|
||||
|
||||
const user = await this.usersService.findUserByEmail(email);
|
||||
|
||||
if (O.isNone(user)) {
|
||||
const createdUser = await this.usersService.createUserSSO(
|
||||
|
|
@ -39,7 +49,7 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
|
|||
}
|
||||
|
||||
/**
|
||||
* * displayName and photoURL maybe null if user logged-in via magic-link before SSO
|
||||
* displayName and photoURL maybe null if user logged-in via magic-link before SSO
|
||||
*/
|
||||
if (!user.value.displayName || !user.value.photoURL) {
|
||||
const updatedUser = await this.usersService.updateUserDetails(
|
||||
|
|
@ -52,8 +62,8 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
|
|||
}
|
||||
|
||||
/**
|
||||
* * Check to see if entry for Microsoft is present in the Account table for user
|
||||
* * If user was created with another provider findUserByEmail may return true
|
||||
* Check to see if entry for Microsoft is present in the Account table for user
|
||||
* If user was created with another provider findUserByEmail may return true
|
||||
*/
|
||||
const providerAccountExists =
|
||||
await this.authService.checkIfProviderAccountExists(user.value, profile);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export class RTJwtStrategy extends PassportStrategy(Strategy, 'jwt-refresh') {
|
|||
super({
|
||||
jwtFromRequest: ExtractJwt.fromExtractors([
|
||||
(request: Request) => {
|
||||
const RTCookie = request.cookies['refresh_token'];
|
||||
const RTCookie = request.cookies?.['refresh_token'];
|
||||
if (!RTCookie) {
|
||||
console.error('`refresh_token` not found');
|
||||
throw new ForbiddenException(COOKIES_NOT_FOUND);
|
||||
|
|
@ -33,7 +33,7 @@ export class RTJwtStrategy extends PassportStrategy(Strategy, 'jwt-refresh') {
|
|||
return RTCookie;
|
||||
},
|
||||
]),
|
||||
secretOrKey: configService.get('JWT_SECRET'),
|
||||
secretOrKey: configService.get<string>('JWT_SECRET'),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@ export const AUTH_PROVIDER_NOT_SPECIFIED = 'auth/provider_not_specified';
|
|||
export const AUTH_PROVIDER_NOT_CONFIGURED =
|
||||
'auth/provider_not_configured_correctly';
|
||||
|
||||
/**
|
||||
* Email not provided by OAuth provider
|
||||
* (SSO Strategies)
|
||||
*/
|
||||
export const AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH =
|
||||
'auth/email_not_provided_by_oauth';
|
||||
|
||||
/**
|
||||
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is not present in .env file
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue