From da77bbca71365807ded7a85fb893247ba6a86240 Mon Sep 17 00:00:00 2001 From: Thiago Ramalho Date: Mon, 19 Aug 2024 21:40:13 -0300 Subject: [PATCH] chore: improvements on github --- .../src/auth-github.strategy.ts | 23 +++++++++------- .../src/config/auth-github-default.config.ts | 2 ++ .../auth-github-missing-email.exception.ts | 14 ++++++++++ .../auth-github-missing-id.exception.ts | 12 +++++++++ .../auth-github-emails.interface.ts | 3 +++ .../auth-github-profile.interface.ts | 9 +++++++ .../auth-github-settings.interface.ts | 5 ++++ .../interfaces/auth-github-sign.interface.ts | 5 ++++ .../nestjs-auth-github/src/utils/index.ts | 19 ++++++++++++++ .../federated-user-relationship.exception.ts | 26 +++++++++++++++++++ .../src/services/federated-oauth.service.ts | 6 +++++ .../src/services/federated.service.ts | 1 + 12 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 packages/nestjs-auth-github/src/exceptions/auth-github-missing-email.exception.ts create mode 100644 packages/nestjs-auth-github/src/exceptions/auth-github-missing-id.exception.ts create mode 100644 packages/nestjs-auth-github/src/interfaces/auth-github-emails.interface.ts create mode 100644 packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts create mode 100644 packages/nestjs-auth-github/src/interfaces/auth-github-sign.interface.ts create mode 100644 packages/nestjs-auth-github/src/utils/index.ts create mode 100644 packages/nestjs-federated/src/exceptions/federated-user-relationship.exception.ts diff --git a/packages/nestjs-auth-github/src/auth-github.strategy.ts b/packages/nestjs-auth-github/src/auth-github.strategy.ts index 29613f5da..001736e5f 100644 --- a/packages/nestjs-auth-github/src/auth-github.strategy.ts +++ b/packages/nestjs-auth-github/src/auth-github.strategy.ts @@ -2,11 +2,6 @@ import { Strategy } from 'passport-github'; import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; -import { - ReferenceEmailInterface, - ReferenceIdInterface, -} from '@concepta/ts-core'; - import { FederatedOAuthService, FederatedCredentialsInterface, @@ -18,6 +13,9 @@ import { } from './auth-github.constants'; import { AuthGithubSettingsInterface } from './interfaces/auth-github-settings.interface'; +import { AuthGithubProfileInterface } from './interfaces/auth-github-profile.interface'; +import { AuthGithubMissingEmailException } from './exceptions/auth-github-missing-email.exception'; +import { AuthGithubMissingIdException } from './exceptions/auth-github-missing-id.exception'; @Injectable() export class AuthGithubStrategy extends PassportStrategy( @@ -26,7 +24,7 @@ export class AuthGithubStrategy extends PassportStrategy( ) { constructor( @Inject(AUTH_GITHUB_MODULE_SETTINGS_TOKEN) - settings: AuthGithubSettingsInterface, + private settings: AuthGithubSettingsInterface, private federatedOAuthService: FederatedOAuthService, ) { super({ @@ -39,15 +37,22 @@ export class AuthGithubStrategy extends PassportStrategy( async validate( _accessToken: string, _refreshToken: string, - profile: ReferenceIdInterface & ReferenceEmailInterface, + profile: AuthGithubProfileInterface & Record, ): Promise { // TODO: should we save accessToken and refreshToken? + const gitProfile = + this.settings.profileFormatter && this.settings.profileFormatter(profile); + + if (!gitProfile?.id) throw new AuthGithubMissingIdException(); + + if (!gitProfile?.email) throw new AuthGithubMissingEmailException(); + // Create a new user if it doesn't exist or just return based on federated const user = await this.federatedOAuthService.sign( AUTH_GITHUB_STRATEGY_NAME, - profile.email, - profile.id, + gitProfile.email, + gitProfile.id, ); if (!user) { diff --git a/packages/nestjs-auth-github/src/config/auth-github-default.config.ts b/packages/nestjs-auth-github/src/config/auth-github-default.config.ts index 0709f45cc..344629422 100644 --- a/packages/nestjs-auth-github/src/config/auth-github-default.config.ts +++ b/packages/nestjs-auth-github/src/config/auth-github-default.config.ts @@ -2,6 +2,7 @@ import { registerAs } from '@nestjs/config'; import { AUTH_GITHUB_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-github.constants'; import { AuthGithubSettingsInterface } from '../interfaces/auth-github-settings.interface'; import { AuthGithubLoginDto } from '../dto/auth-github-login.dto'; +import { profileFormatter } from '../utils'; /** * Default configuration for auth github. @@ -16,5 +17,6 @@ export const authGithubDefaultConfig = registerAs( clientId: process.env.GITHUB_CLIENT_ID ?? 'client_id', clientSecret: process.env.GITHUB_CLIENT_SECRET ?? 'secret', callbackURL: process.env.GITHUB_CALLBACK_URL ?? 'callback_url', + profileFormatter, }), ); diff --git a/packages/nestjs-auth-github/src/exceptions/auth-github-missing-email.exception.ts b/packages/nestjs-auth-github/src/exceptions/auth-github-missing-email.exception.ts new file mode 100644 index 000000000..ba4e3cb93 --- /dev/null +++ b/packages/nestjs-auth-github/src/exceptions/auth-github-missing-email.exception.ts @@ -0,0 +1,14 @@ +import { ExceptionInterface } from '@concepta/ts-core'; + +export class AuthGithubMissingEmailException + extends Error + implements ExceptionInterface +{ + errorCode = 'AUTH_GITHUB_MISSING_PROFILE_EMAIL_ERROR'; + + constructor( + message = 'GitHub did not return an email address for the user.', + ) { + super(message); + } +} diff --git a/packages/nestjs-auth-github/src/exceptions/auth-github-missing-id.exception.ts b/packages/nestjs-auth-github/src/exceptions/auth-github-missing-id.exception.ts new file mode 100644 index 000000000..22983c431 --- /dev/null +++ b/packages/nestjs-auth-github/src/exceptions/auth-github-missing-id.exception.ts @@ -0,0 +1,12 @@ +import { ExceptionInterface } from '@concepta/ts-core'; + +export class AuthGithubMissingIdException + extends Error + implements ExceptionInterface +{ + errorCode = 'AUTH_GITHUB_MISSING_PROFILE_ID_ERROR'; + + constructor(message = 'GitHub did not return an id for the user.') { + super(message); + } +} diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-emails.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-emails.interface.ts new file mode 100644 index 000000000..9f70e5b20 --- /dev/null +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-emails.interface.ts @@ -0,0 +1,3 @@ +export interface AuthGithubEmailsInterface { + value: string; +} diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts new file mode 100644 index 000000000..86d765d01 --- /dev/null +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts @@ -0,0 +1,9 @@ +import { ReferenceIdInterface } from '@concepta/ts-core'; +import { AuthGithubEmailsInterface } from './auth-github-emails.interface'; + +export interface AuthGithubProfileInterface extends ReferenceIdInterface { + displayName?: string; + username?: string; + email?: string; + emails?: AuthGithubEmailsInterface[]; +} diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-settings.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-settings.interface.ts index b7fbf970a..08420724c 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-settings.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-settings.interface.ts @@ -1,9 +1,14 @@ import { AuthenticationCodeInterface } from '@concepta/ts-common'; import { Type } from '@nestjs/common'; +import { AuthGithubProfileInterface } from './auth-github-profile.interface'; +import { AuthGithubSignInterface } from './auth-github-sign.interface'; export interface AuthGithubSettingsInterface { clientId: string; clientSecret: string; callbackURL: string; loginDto?: Type; + profileFormatter: ( + profile: AuthGithubProfileInterface, + ) => AuthGithubSignInterface; } diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-sign.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-sign.interface.ts new file mode 100644 index 000000000..0939f29d2 --- /dev/null +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-sign.interface.ts @@ -0,0 +1,5 @@ +import { ReferenceIdInterface } from '@concepta/ts-core'; + +export interface AuthGithubSignInterface extends ReferenceIdInterface { + email: string; +} diff --git a/packages/nestjs-auth-github/src/utils/index.ts b/packages/nestjs-auth-github/src/utils/index.ts new file mode 100644 index 000000000..452d58add --- /dev/null +++ b/packages/nestjs-auth-github/src/utils/index.ts @@ -0,0 +1,19 @@ +import { AuthGithubProfileInterface } from '../interfaces/auth-github-profile.interface'; +import { AuthGithubSignInterface } from '../interfaces/auth-github-sign.interface'; + +export const profileFormatter = ( + profile: AuthGithubProfileInterface, +): AuthGithubSignInterface => { + let email = ''; + + if (profile.email) email = profile.email; + else if (profile.emails && profile.emails.length > 0) { + email = profile.emails[0].value; + } + + const result: AuthGithubSignInterface = { + id: profile?.id || '', + email, + }; + return result; +}; diff --git a/packages/nestjs-federated/src/exceptions/federated-user-relationship.exception.ts b/packages/nestjs-federated/src/exceptions/federated-user-relationship.exception.ts new file mode 100644 index 000000000..e70f78265 --- /dev/null +++ b/packages/nestjs-federated/src/exceptions/federated-user-relationship.exception.ts @@ -0,0 +1,26 @@ +import { format } from 'util'; +import { ExceptionInterface } from '@concepta/ts-core'; + +export class FederatedUserRelationshipException + extends Error + implements ExceptionInterface +{ + errorCode = 'FEDERATED_USER_RELATIONSHIP_ERROR'; + + context: { + entityName: string; + federatedId: string; + }; + + constructor( + entityName: string, + federatedId: string, + message = 'Error while trying to load user relationship from federated $s', + ) { + super(format(message, federatedId)); + this.context = { + entityName, + federatedId, + }; + } +} diff --git a/packages/nestjs-federated/src/services/federated-oauth.service.ts b/packages/nestjs-federated/src/services/federated-oauth.service.ts index 90130ad5b..f5945eb88 100644 --- a/packages/nestjs-federated/src/services/federated-oauth.service.ts +++ b/packages/nestjs-federated/src/services/federated-oauth.service.ts @@ -59,6 +59,12 @@ export class FederatedOAuthService implements FederatedOAuthServiceInterface { queryOptions, ); } else { + if (!federated.user?.id) + throw new FederatedUserLookupException( + this.constructor.name, + federated.user, + ); + const user = await this.userLookupService.byId( federated.user.id, queryOptions, diff --git a/packages/nestjs-federated/src/services/federated.service.ts b/packages/nestjs-federated/src/services/federated.service.ts index 458a1048d..cbd91244b 100644 --- a/packages/nestjs-federated/src/services/federated.service.ts +++ b/packages/nestjs-federated/src/services/federated.service.ts @@ -33,6 +33,7 @@ export class FederatedService provider, subject, }, + relations: ['user'], }); } catch (e) { const exception = e instanceof Error ? e : new NotAnErrorException(e);