Skip to content

Commit

Permalink
Merge pull request #91 from skgndi12/feature/issue-88/implement-auth-…
Browse files Browse the repository at this point in the history
…service

[#88] Implement Google OIDC Start Service
  • Loading branch information
skgndi12 authored Dec 26, 2023
2 parents c9791d8 + 716df47 commit 8c56aa4
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 0 deletions.
6 changes: 6 additions & 0 deletions api/src/config/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ConfigTimeout
} from '@src/config/types';
import { HttpConfig } from '@src/controller/http/types';
import { AuthConfig } from '@src/core/services/auth/types';
import { GoogleClientConfig } from '@src/infrastructure/google/types';
import { DatabaseConfig } from '@src/infrastructure/repositories/types';
import { JwtClientConfig } from '@src/jwt/types';
Expand Down Expand Up @@ -74,3 +75,8 @@ export function buildGoogleClientConfig(config: Config): GoogleClientConfig {
};
}

export function buildAuthConfig(config: Config): AuthConfig {
return {
oauthStateExpirationMinutes: config.oauth.stateExpirationMinutes
};
}
27 changes: 27 additions & 0 deletions api/src/core/services/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { randomUUID } from 'crypto';

import { GoogleHandler } from '@src/core/ports/google.handler';
import { KeyValueRepository } from '@src/core/ports/keyValue.repository';
import { AuthConfig } from '@src/core/services/auth/types';

export class AuthService {
constructor(
private readonly config: AuthConfig,
private readonly keyValueRepository: KeyValueRepository,
private readonly googleHandler: GoogleHandler
) {}

public initiateGoogleSignIn = (
protocol: string,
referrer: string | null
): string => {
const state = randomUUID();
const stateTokenString = JSON.stringify({ state, referrer });
this.keyValueRepository.set(
state,
stateTokenString,
this.config.oauthStateExpirationMinutes * 60
);
return this.googleHandler.buildOidcRequest(protocol, state);
};
}
3 changes: 3 additions & 0 deletions api/src/core/services/auth/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface AuthConfig {
oauthStateExpirationMinutes: number;
}
9 changes: 9 additions & 0 deletions api/test/config/loader.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import config from 'config';

import {
buildAuthConfig,
buildDatabaseConfig,
buildGoogleClientConfig,
buildHttpConfig,
Expand Down Expand Up @@ -130,3 +131,11 @@ describe('Test build google client config', () => {
});
});
});

describe('Test build auth config', () => {
it('should build valid auth config from a test.yaml', () => {
expect(buildAuthConfig(loadConfig())).toStrictEqual({
oauthStateExpirationMinutes: 10
});
});
});
84 changes: 84 additions & 0 deletions api/test/core/services/auth/auth.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as crypto from 'crypto';
import { parse } from 'querystring';

import { GoogleHandler } from '@src/core/ports/google.handler';
import { KeyValueRepository } from '@src/core/ports/keyValue.repository';
import { AuthService } from '@src/core/services/auth/auth.service';
import { AuthConfig } from '@src/core/services/auth/types';

jest.mock('crypto');

describe('Test auth service', () => {
const oauthClientId = 'test_client_id';
const oauthRedirectUri = '127.0.0.1:0/api/v1/google/sign-in/token';
const oauthAuthEndpoint = 'https://accounts.google.com/o/oauth2/auth';
const nonce = '2fc312e4-0c04-4f93-b0bf-c9e81f244814';
const state = '8a5c8a05-0a20-49f9-92a5-a6f83d5617e9';
const response_type = 'code';
const protocol = 'http';
const scope = 'openid email';
const access_type = 'online';
const prompt = 'consent select_account';
const authConfig: AuthConfig = { oauthStateExpirationMinutes: 10 };
let keyValueRepository: KeyValueRepository;
let googleHandler: GoogleHandler;
let authService: AuthService;

beforeEach(() => {
keyValueRepository = {
set: jest.fn(),
get: jest.fn()
};
googleHandler = {
buildOidcRequest: jest.fn(() => {
return encodeURI(
`${oauthAuthEndpoint}?client_id=${oauthClientId}&nonce=${nonce}&response_type=${response_type}&redirect_uri=${protocol}://${oauthRedirectUri}&scope=${scope}&state=${state}&access_type=${access_type}&prompt=${prompt}`
);
})
};
jest.spyOn(crypto, 'randomUUID').mockReturnValue(state);
authService = new AuthService(
authConfig,
keyValueRepository,
googleHandler
);
});

it('should return valid OIDC request', () => {
const givenReferrer = 'http://127.0.0.1/home';
const expectedQueyObject = {
client_id: oauthClientId,
response_type,
redirect_uri: `${protocol}://${oauthRedirectUri}`,
state,
scope,
access_type,
prompt
};

const redirectUrl = authService.initiateGoogleSignIn(
protocol,
givenReferrer
);
const [authEndpoint, queryString] = redirectUrl.split('?');
const actualQueryObject = parse(queryString);

expect(authEndpoint).toEqual(oauthAuthEndpoint);
expect(actualQueryObject).toEqual(
expect.objectContaining(expectedQueyObject)
);

expect(keyValueRepository.set).toBeCalledTimes(1);
expect(keyValueRepository.set).toBeCalledWith(
actualQueryObject.state,
JSON.stringify({
state: state,
referrer: givenReferrer
}),
authConfig.oauthStateExpirationMinutes * 60
);

expect(googleHandler.buildOidcRequest).toBeCalledTimes(1);
expect(googleHandler.buildOidcRequest).toBeCalledWith(protocol, state);
});
});

0 comments on commit 8c56aa4

Please sign in to comment.