Skip to content

Commit

Permalink
chore: improvements on github
Browse files Browse the repository at this point in the history
  • Loading branch information
tnramalho committed Aug 20, 2024
1 parent 7dcf4cb commit da77bbc
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 9 deletions.
23 changes: 14 additions & 9 deletions packages/nestjs-auth-github/src/auth-github.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(
Expand All @@ -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({
Expand All @@ -39,15 +37,22 @@ export class AuthGithubStrategy extends PassportStrategy(
async validate(
_accessToken: string,
_refreshToken: string,
profile: ReferenceIdInterface & ReferenceEmailInterface,
profile: AuthGithubProfileInterface & Record<string, string>,
): Promise<FederatedCredentialsInterface> {
// 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
}),
);
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface AuthGithubEmailsInterface {
value: string;
}
Original file line number Diff line number Diff line change
@@ -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[];
}
Original file line number Diff line number Diff line change
@@ -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<AuthenticationCodeInterface>;
profileFormatter: (
profile: AuthGithubProfileInterface,
) => AuthGithubSignInterface;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ReferenceIdInterface } from '@concepta/ts-core';

export interface AuthGithubSignInterface extends ReferenceIdInterface {
email: string;
}
19 changes: 19 additions & 0 deletions packages/nestjs-auth-github/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
@@ -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,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class FederatedService
provider,
subject,
},
relations: ['user'],
});
} catch (e) {
const exception = e instanceof Error ? e : new NotAnErrorException(e);
Expand Down

0 comments on commit da77bbc

Please sign in to comment.