Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tnramalho feature/GitHub adjustments #227

Merged
merged 6 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/nestjs-auth-github/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@concepta/nestjs-authentication": "^5.0.0-alpha.0",
"@concepta/nestjs-common": "^5.0.0-alpha.0",
"@concepta/nestjs-core": "^5.0.0-alpha.0",
"@concepta/nestjs-exception": "^5.0.0-alpha.0",
"@concepta/nestjs-federated": "^5.0.0-alpha.0",
"@concepta/ts-common": "^5.0.0-alpha.0",
"@concepta/ts-core": "^5.0.0-alpha.0",
Expand Down
29 changes: 19 additions & 10 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,10 @@ 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';
import { mapProfile } from './utils/auth-github-map-profile';

@Injectable()
export class AuthGithubStrategy extends PassportStrategy(
Expand All @@ -26,7 +25,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 +38,25 @@ export class AuthGithubStrategy extends PassportStrategy(
async validate(
_accessToken: string,
_refreshToken: string,
profile: ReferenceIdInterface & ReferenceEmailInterface,
profile: AuthGithubProfileInterface,
): Promise<FederatedCredentialsInterface> {
// TODO: should we save accessToken and refreshToken?
const gitProfile = this.settings.mapProfile
? this.settings.mapProfile(profile)
: mapProfile(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 { mapProfile } from '../utils/auth-github-map-profile';

/**
* 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',
mapProfile: mapProfile,
}),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { RuntimeException } from '@concepta/nestjs-exception';

export class AuthGithubMissingEmailException extends RuntimeException {
constructor(
message = 'GitHub did not return an email address for the user.',
) {
super(message);
this.errorCode = 'AUTH_GITHUB_MISSING_PROFILE_EMAIL_ERROR';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { RuntimeException } from '@concepta/nestjs-exception';

export class AuthGithubMissingIdException extends RuntimeException {
constructor(message = 'GitHub did not return an id for the user.') {
super(message);
this.errorCode = 'AUTH_GITHUB_MISSING_PROFILE_ID_ERROR';
}
}
2 changes: 2 additions & 0 deletions packages/nestjs-auth-github/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * from './auth-github.module';
export * from './auth-github.controller';
export * from './dto/auth-github-login.dto';
export * from './interfaces/auth-github-map-profile.type';

export {
AuthGithubGuard,
AuthGithubGuard as GithubAuthGuard,
Expand Down
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,6 @@
import { AuthGithubProfileInterface } from './auth-github-profile.interface';
import { AuthGithubSignInterface } from './auth-github-sign.interface';

export type MapProfile = (
profile: AuthGithubProfileInterface,
) => AuthGithubSignInterface;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
ReferenceEmailInterface,
ReferenceIdInterface,
ReferenceUsernameInterface,
} from '@concepta/ts-core';
import { AuthGithubEmailsInterface } from './auth-github-emails.interface';

export interface AuthGithubProfileInterface
extends ReferenceIdInterface,
Partial<ReferenceEmailInterface>,
Partial<ReferenceUsernameInterface> {
displayName?: string;
emails?: AuthGithubEmailsInterface[];
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { AuthenticationCodeInterface } from '@concepta/ts-common';
import { Type } from '@nestjs/common';
import { MapProfile } from './auth-github-map-profile.type';

export interface AuthGithubSettingsInterface {
clientId: string;
clientSecret: string;
callbackURL: string;
loginDto?: Type<AuthenticationCodeInterface>;
mapProfile: MapProfile;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {
ReferenceEmailInterface,
ReferenceIdInterface,
} from '@concepta/ts-core';

export interface AuthGithubSignInterface
extends ReferenceIdInterface,
ReferenceEmailInterface {}
20 changes: 20 additions & 0 deletions packages/nestjs-auth-github/src/utils/auth-github-map-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AuthGithubProfileInterface } from '../interfaces/auth-github-profile.interface';
import { AuthGithubSignInterface } from '../interfaces/auth-github-sign.interface';

export const mapProfile = (
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;
};
3 changes: 3 additions & 0 deletions packages/nestjs-auth-github/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"references": [
{
"path": "../nestjs-federated"
},
{
"path": "../nestjs-exception"
}
]
}
1 change: 1 addition & 0 deletions packages/nestjs-federated/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
],
"dependencies": {
"@concepta/nestjs-common": "^5.0.0-alpha.0",
"@concepta/nestjs-exception": "^5.0.0-alpha.0",
"@concepta/nestjs-typeorm-ext": "^5.0.0-alpha.0",
"@concepta/ts-common": "^5.0.0-alpha.0",
"@concepta/ts-core": "^5.0.0-alpha.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { format } from 'util';
import { ExceptionInterface, ReferenceIdInterface } from '@concepta/ts-core';
import { ReferenceIdInterface } from '@concepta/ts-core';
import { RuntimeException } from '@concepta/nestjs-exception';
import { HttpStatus } from '@nestjs/common';

export class FederatedUserLookupException
extends Error
implements ExceptionInterface
{
errorCode = 'FEDERATED_USER_LOOKUP_ERROR';

context: {
export class FederatedUserLookupException extends RuntimeException {
context: RuntimeException['context'] & {
entityName: string;
user: ReferenceIdInterface;
};

constructor(
entityName: string,
user: ReferenceIdInterface,
message = 'Error while trying find user $s',
message = 'Error while trying find user %s',
) {
super(format(message, user));
super({
message,
messageParams: [user.id],
httpStatus: HttpStatus.NOT_FOUND,
});
this.errorCode = 'FEDERATED_USER_LOOKUP_ERROR';
this.context = {
...super.context,
entityName,
user,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { RuntimeException } from '@concepta/nestjs-exception';
import { HttpStatus } from '@nestjs/common';
export class FederatedUserRelationshipException extends RuntimeException {
context: RuntimeException['context'] & {
federatedId: string;
};

constructor(
federatedId: string,
message = 'Error while trying to load user relationship from federated %s',
) {
super({
message,
messageParams: [federatedId],
httpStatus: HttpStatus.NOT_FOUND,
});

this.errorCode = 'FEDERATED_USER_RELATIONSHIP_ERROR';

this.context = {
...super.context,
federatedId,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { FederatedCreateException } from '../exceptions/federated-create.excepti
import { FederatedMutateCreateUserException } from '../exceptions/federated-mutate-create.exception';
import { FederatedUserLookupException } from '../exceptions/federated-user-lookup.exception';
import { FederatedMutateService } from './federated-mutate.service';
import { FederatedUserRelationshipException } from '../exceptions/federated-user-relationship.exception';

@Injectable()
export class FederatedOAuthService implements FederatedOAuthServiceInterface {
Expand Down Expand Up @@ -59,16 +60,24 @@ export class FederatedOAuthService implements FederatedOAuthServiceInterface {
queryOptions,
);
} else {
if (!federated.user?.id) {
throw new FederatedUserRelationshipException(
this.constructor.name,
federated.id,
);
}

const user = await this.userLookupService.byId(
federated.user.id,
queryOptions,
);

if (!user)
if (!user) {
throw new FederatedUserLookupException(
this.constructor.name,
federated.user,
);
}

return user;
}
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
Loading
Loading