Skip to content

Commit

Permalink
Merge branch 'bug/report-address-format' of https://github.com/bcgov/…
Browse files Browse the repository at this point in the history
…fin-pay-transparency into bug/report-address-format
  • Loading branch information
banders committed May 7, 2024
2 parents 6d35a4c + 9b20c20 commit fba2ded
Show file tree
Hide file tree
Showing 20 changed files with 915 additions and 875 deletions.
274 changes: 137 additions & 137 deletions backend-external/package-lock.json

Large diffs are not rendered by default.

377 changes: 169 additions & 208 deletions backend/package-lock.json

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,19 @@
"ecmaVersion": 2022,
"sourceType": "module"
},
"rules": {}
"rules": {
"jest/expect-expect": [
"error",
{
"assertFunctionNames": [
[
"expect",
"request.**.expect"
]
]
}
]
}
},
"eslintIgnore": [],
"prettier": {
Expand Down
50 changes: 45 additions & 5 deletions backend/src/v1/routes/auth-routes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import express, { Application } from 'express';
import router from './auth-routes';
import router, { LogoutReason } from './auth-routes';
import request from 'supertest';
import { MISSING_COMPANY_DETAILS_ERROR } from '../../constants';
let app: Application;
Expand Down Expand Up @@ -66,16 +66,38 @@ describe('auth-routes', () => {
});

describe('/callback_business_bceid [GET]', () => {
it('should execute handleCallBackBusinessBceid', () => {
it('should redirect to frontend if successful', () => {
mockAuthenticate.mockImplementation((_, __, next) => {
next();
});

mockHandleCallbackBusinessBceid.mockImplementation((req, res) => {
return res.status(200).send({ success: true });
mockHandleCallbackBusinessBceid.mockImplementation((req) => {
return LogoutReason.Login;
});

return request(app).get('/callback_business_bceid').expect(200);
return request(app).get('/callback_business_bceid').expect(302);
});
it('should log out if error', () => {
mockLogout.mockImplementation((cb) => cb());
mockGetOidcDiscovery.mockResolvedValue({
end_session_endpoint: 'http://test.com/',
});
app.use((req: any, res, next) => {
req.session = mockRequest.session;
req.user = mockRequest.user;
req.logout = mockRequest.logout;
next();
});
app.use('/auth', router);
mockAuthenticate.mockImplementation((_, __, next) => {
next();
});

mockHandleCallbackBusinessBceid.mockImplementation((req) => {
return LogoutReason.ContactError;
});

return request(app).get('/auth/callback_business_bceid').expect(302);
});
});

Expand Down Expand Up @@ -120,6 +142,24 @@ describe('auth-routes', () => {
.query({ sessionExpired: true })
.expect(302);
});
it('should handle contact error', () => {
mockLogout.mockImplementation((cb) => cb());
mockGetOidcDiscovery.mockResolvedValue({
end_session_endpoint: 'http://test.com/',
});
app.use((req: any, res, next) => {
req.session = mockRequest.session;
req.user = mockRequest.user;
req.logout = mockRequest.logout;
next();
});
app.use('/auth', router);

return request(app)
.get('/auth/logout')
.query({ contactError: true })
.expect(302);
});
it('should handle loginBceid', () => {
mockLogout.mockImplementation((cb) => cb());
mockGetOidcDiscovery.mockResolvedValue({
Expand Down
133 changes: 70 additions & 63 deletions backend/src/v1/routes/auth-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ router.get(
passport.authenticate('oidcBusinessBceid', {
failureMessage: true,
}),
utils.asyncHandler(async (req: Request, res: Response) => {
log.debug(`Login flow callback business bceid is called.`);
await auth.handleCallBackBusinessBceid(req, res);
}),
utils.asyncHandler(
async (req: Request, res: Response, next: NextFunction) => {
log.debug(`Login flow callback business bceid is called.`);
const logoutReason = await auth.handleCallBackBusinessBceid(req);
if (logoutReason == LogoutReason.Login)
return res.redirect(config.get('server:frontend'));
else return logoutHandler(req, res, next, logoutReason);
},
),
);

//a prettier way to handle errors
router.get('/error', (req, res) => {
log.error(`Login flow Error happened`);
Expand All @@ -41,69 +47,70 @@ function addBaseRouterGet(strategyName, callbackURI) {

addBaseRouterGet('oidcBusinessBceid', '/login_bceid');

export enum LogoutReason {
Login = 'login', // ie. don't log out
Default = 'default',
SessionExpired = 'sessionExpired',
LoginError = 'loginError',
LoginBceid = 'loginBceid',
ContactError = 'contactError',
}

//removes tokens and destroys session
async function logoutHandler(
req: Request,
res: Response,
next: NextFunction,
reason: LogoutReason,
) {
// @ts-expect-error, it is given by passport lib.
const idToken: string = req['user']?.idToken;
const discovery = await utils.getOidcDiscovery();
req.logout(function (err) {
if (err) {
return next(err);
}
req.session.destroy((err) => {
if (err) {
log.error(`Logout failed - ${err.message}`);
return next(err);
}
});

if (idToken) {
let url = '';

if (reason == 'sessionExpired') url = '/session-expired';
else if (reason == 'loginError') url = '/login-error';
else if (reason == 'loginBceid') url = '/api/auth/login_bceid';
else if (reason == 'contactError') url = '/contact-error';
else url = '/logout';

const retUrl = encodeURIComponent(
discovery.end_session_endpoint +
'?post_logout_redirect_uri=' +
config.get('server:frontend') +
url +
'&id_token_hint=' +
idToken,
);
res.redirect(config.get('siteMinder_logout_endpoint') + retUrl);
} else {
res.redirect(config.get('server:frontend') + '/api/auth/login_bceid');
}
});
}

router.get(
'/logout',
utils.asyncHandler(
async (req: Request, res: Response, next: NextFunction) => {
// @ts-ignore, it is given by passport lib.
const idToken: string = req['user']?.idToken;
const discovery = await utils.getOidcDiscovery();
req.logout(function (err) {
if (err) {
return next(err);
}
req.session.destroy((err) => {
if (err) {
log.error(`Logout failed - ${err.message}`);
return next(err);
}
});

let retUrl;
if (idToken) {
if (req.query?.sessionExpired) {
retUrl = encodeURIComponent(
discovery.end_session_endpoint +
'?post_logout_redirect_uri=' +
config.get('server:frontend') +
'/session-expired' +
'&id_token_hint=' +
idToken,
);
} else if (req.query?.loginError) {
retUrl = encodeURIComponent(
discovery.end_session_endpoint +
'?post_logout_redirect_uri=' +
config.get('server:frontend') +
'/login-error' +
'&id_token_hint=' +
idToken,
);
} else if (req.query?.loginBceid) {
retUrl = encodeURIComponent(
discovery.end_session_endpoint +
'?post_logout_redirect_uri=' +
config.get('server:frontend') +
'/api/auth/login_bceid' +
'&id_token_hint=' +
idToken,
);
} else {
retUrl = encodeURIComponent(
discovery.end_session_endpoint +
'?post_logout_redirect_uri=' +
config.get('server:frontend') +
'/logout' +
'&id_token_hint=' +
idToken,
);
}
res.redirect(config.get('siteMinder_logout_endpoint') + retUrl);
} else {
res.redirect(config.get('server:frontend') + '/api/auth/login_bceid');
}
});
let reason: LogoutReason = LogoutReason.Default;
if (req.query?.sessionExpired) reason = LogoutReason.SessionExpired;
else if (req.query?.loginError) reason = LogoutReason.LoginError;
else if (req.query?.loginBceid) reason = LogoutReason.LoginBceid;
else if (req.query?.contactError) reason = LogoutReason.ContactError;
await logoutHandler(req, res, next, reason);
},
),
);
Expand Down Expand Up @@ -132,7 +139,7 @@ router.post(
`${MISSING_COMPANY_DETAILS_ERROR} in session. No correlation id found.`,
);
}

return res.status(401).json({ error: MISSING_COMPANY_DETAILS_ERROR });
}

Expand Down
24 changes: 10 additions & 14 deletions backend/src/v1/services/auth-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { auth } from './auth-service';
import { utils } from './utils-service';
import prisma from '../prisma/prisma-client';
import * as bceidService from '../../external/services/bceid-service';
import { LogoutReason } from '../routes/auth-routes';
//Mock the entire axios module so we never inadvertently make real
//HTTP calls to remote services
jest.mock('axios');
Expand Down Expand Up @@ -575,15 +576,10 @@ describe('handleCallBackBusinessBceid', () => {
});
});
it('should handle the callback successfully', async () => {
// Mock any dependencies and set up the expected behavior
const result = await auth.handleCallBackBusinessBceid(req);

// Act
await auth.handleCallBackBusinessBceid(req, res);

expect(res.redirect).toHaveBeenCalled();
expect(result).toBe(LogoutReason.Login);
expect(req.session.companyDetails).toStrictEqual(mockCompanyInSession);
// Assert
// Verify the function behaved as expected
});

it('should Add the company details to session if it is not present and add it to db', async () => {
Expand All @@ -601,8 +597,8 @@ describe('handleCallBackBusinessBceid', () => {
...req,
};
modifiedReq.session.companyDetails = undefined;
await auth.handleCallBackBusinessBceid(modifiedReq, res);
expect(res.redirect).toHaveBeenCalled();
const result = await auth.handleCallBackBusinessBceid(modifiedReq);
expect(result).toBe(LogoutReason.Login);
expect(req.session.companyDetails).toStrictEqual(mockCompanyInSession);
expect(prisma.$transaction).toHaveBeenCalled();
expect(prisma.pay_transparency_company.findFirst).toHaveBeenCalled();
Expand All @@ -629,9 +625,9 @@ describe('handleCallBackBusinessBceid', () => {
...req,
};
modifiedReq.session.companyDetails = undefined;
await auth.handleCallBackBusinessBceid(modifiedReq, res);
expect(res.redirect).toHaveBeenCalled();
expect(req.session.companyDetails).toStrictEqual(mockCompanyInSession);
const result = await auth.handleCallBackBusinessBceid(modifiedReq);
expect(result).toBe(LogoutReason.LoginError);
expect(req.session.companyDetails).toBeUndefined();
expect(prisma.$transaction).toHaveBeenCalled();
expect(prisma.pay_transparency_company.findFirst).toHaveBeenCalled();
expect(prisma.pay_transparency_company.update).toHaveBeenCalled();
Expand Down Expand Up @@ -659,8 +655,8 @@ describe('handleCallBackBusinessBceid', () => {
...req,
};
modifiedReq.session.companyDetails = undefined;
await auth.handleCallBackBusinessBceid(modifiedReq, res);
expect(res.redirect).toHaveBeenCalled();
const result = await auth.handleCallBackBusinessBceid(modifiedReq);
expect(result).toBe(LogoutReason.ContactError);
expect(req.session.companyDetails).toBeUndefined();
expect(prisma.$transaction).toHaveBeenCalledTimes(0);
expect(prisma.pay_transparency_company.findFirst).toHaveBeenCalledTimes(0);
Expand Down
20 changes: 10 additions & 10 deletions backend/src/v1/services/auth-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import prisma, { PrismaTransactionalClient } from '../prisma/prisma-client';
import { getCompanyDetails } from '../../external/services/bceid-service';
import { Request, NextFunction, Response } from 'express';
import { pay_transparency_company } from '@prisma/client';
import { LogoutReason } from '../routes/auth-routes';

let kcPublicKey: string;
const auth = {
Expand Down Expand Up @@ -317,32 +318,31 @@ const auth = {
}
},

async handleCallBackBusinessBceid(req: Request, res: Response) {
async handleCallBackBusinessBceid(req: Request): Promise<LogoutReason> {
const session: any = req.session;
const userInfo = utils.getSessionUser(req);
const jwtPayload = jsonwebtoken.decode(userInfo.jwt) as JwtPayload;
const userGuid = jwtPayload?.bceid_user_guid;
if (!userGuid) {
log.error(`no bceid_user_guid found in the jwt token`, userInfo.jwt);
return res.redirect(config.get('server:frontend') + '/login-error');
return LogoutReason.LoginError;
}

try {
auth.validateClaims(userInfo.jwt);
} catch (e) {
log.error('invalid claims in token', e);
return res.redirect(config.get('server:frontend') + '/login-error');
return LogoutReason.LoginError;
}

// companyDetails already saved - success
if (session?.companyDetails) {
return res.redirect(config.get('server:frontend'));
return LogoutReason.Login;
}

// companyDetails haven't been saved - get them
let details;
try {
details = await getCompanyDetails(userGuid);
const details = await getCompanyDetails(userGuid);

if (
!details.legalName ||
Expand All @@ -355,20 +355,20 @@ const auth = {
log.error(
`Required company details missing from BCEID for user ${userGuid}`,
);
return res.redirect(config.get('server:frontend') + '/contact-error');
return LogoutReason.ContactError;
}

await auth.storeUserInfo(details, userInfo);
session.companyDetails = details;
await auth.storeUserInfo(session.companyDetails, userInfo);
} catch (e) {
log.error(
`Error happened while getting company details from BCEID for user ${userGuid}`,
e,
);
return res.redirect(config.get('server:frontend') + '/login-error');
return LogoutReason.LoginError;
}

return res.redirect(config.get('server:frontend'));
return LogoutReason.Login;
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ spec:
value: http://{{ .Release.Name }}-backend
- name: LOG_LEVEL
value: {{ .Values.env.logLevel }}
- name: SNOWPLOW_URL
value: {{ .Values.env.snowplowUrl }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
Expand Down
Loading

0 comments on commit fba2ded

Please sign in to comment.