diff --git a/packages/hoppscotch-backend/src/auth/strategies/github.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/github.strategy.ts index 4c2d3b1e..7c085d10 100644 --- a/packages/hoppscotch-backend/src/auth/strategies/github.strategy.ts +++ b/packages/hoppscotch-backend/src/auth/strategies/github.strategy.ts @@ -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('INFRA.GITHUB_CLIENT_ID'), + clientSecret: configService.get('INFRA.GITHUB_CLIENT_SECRET'), + callbackURL: configService.get('INFRA.GITHUB_CALLBACK_URL'), + scope: [configService.get('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); diff --git a/packages/hoppscotch-backend/src/auth/strategies/google.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/google.strategy.ts index 52bb7776..523392c0 100644 --- a/packages/hoppscotch-backend/src/auth/strategies/google.strategy.ts +++ b/packages/hoppscotch-backend/src/auth/strategies/google.strategy.ts @@ -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('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(','), 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); diff --git a/packages/hoppscotch-backend/src/auth/strategies/jwt.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/jwt.strategy.ts index c3bb68bd..322ebef6 100644 --- a/packages/hoppscotch-backend/src/auth/strategies/jwt.strategy.ts +++ b/packages/hoppscotch-backend/src/auth/strategies/jwt.strategy.ts @@ -93,7 +93,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { ), ), ]), - secretOrKey: configService.get('JWT_SECRET'), + secretOrKey: configService.get('JWT_SECRET'), }); } diff --git a/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts index cffa6808..528fb9b2 100644 --- a/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts +++ b/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts @@ -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('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'), 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); diff --git a/packages/hoppscotch-backend/src/auth/strategies/rt-jwt.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/rt-jwt.strategy.ts index c9351aad..e8c57ab8 100644 --- a/packages/hoppscotch-backend/src/auth/strategies/rt-jwt.strategy.ts +++ b/packages/hoppscotch-backend/src/auth/strategies/rt-jwt.strategy.ts @@ -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('JWT_SECRET'), }); } diff --git a/packages/hoppscotch-backend/src/errors.ts b/packages/hoppscotch-backend/src/errors.ts index 0a52d247..1d33a1c1 100644 --- a/packages/hoppscotch-backend/src/errors.ts +++ b/packages/hoppscotch-backend/src/errors.ts @@ -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 */