mirror of https://github.com/boxyhq/jackson.git
172 lines
5.4 KiB
TypeScript
172 lines
5.4 KiB
TypeScript
import crypto from 'crypto';
|
|
import { promises as fs } from 'fs';
|
|
import path from 'path';
|
|
import sinon from 'sinon';
|
|
import tap from 'tap';
|
|
import { IConnectionAPIController, ILogoutController } from '../../src/typings';
|
|
import { relayStatePrefix } from '../../src/controller/utils';
|
|
import { saml_connection } from './fixture';
|
|
import { addSSOConnections, databaseOptions } from '../utils';
|
|
|
|
let connectionAPIController: IConnectionAPIController;
|
|
let logoutController: ILogoutController;
|
|
|
|
const metadataPath = path.join(__dirname, '/data/metadata');
|
|
|
|
tap.before(async () => {
|
|
const controller = await (await import('../../src/index')).default(databaseOptions);
|
|
|
|
connectionAPIController = controller.connectionAPIController;
|
|
logoutController = controller.logoutController;
|
|
|
|
await addSSOConnections(metadataPath, connectionAPIController);
|
|
});
|
|
|
|
tap.teardown(async () => {
|
|
process.exit(0);
|
|
});
|
|
|
|
tap.test('LogoutController -> createRequest', async (t) => {
|
|
const body = {
|
|
nameId: 'google-oauth2|146623609101108149256',
|
|
tenant: saml_connection.tenant,
|
|
product: saml_connection.product,
|
|
redirectUrl: saml_connection.defaultRedirectUrl,
|
|
};
|
|
|
|
t.test('createRequest', async (t) => {
|
|
t.test('Should throw an error if the tenant or product are invalid', async (t) => {
|
|
try {
|
|
await logoutController.createRequest({
|
|
...body,
|
|
tenant: 'invalid-tenant',
|
|
});
|
|
} catch (err: any) {
|
|
t.equal(err.message, 'SAML connection not found.');
|
|
t.equal(err.statusCode, 403);
|
|
}
|
|
|
|
try {
|
|
await logoutController.createRequest({
|
|
...body,
|
|
product: 'invalid-product',
|
|
});
|
|
} catch (err: any) {
|
|
t.equal(err.message, 'SAML connection not found.');
|
|
t.equal(err.statusCode, 403);
|
|
}
|
|
|
|
try {
|
|
await logoutController.createRequest({
|
|
...body,
|
|
tenant: '',
|
|
product: '',
|
|
});
|
|
} catch (err: any) {
|
|
t.equal(err.message, 'SAML connection not found.');
|
|
t.equal(err.statusCode, 403);
|
|
}
|
|
});
|
|
|
|
t.test("Should throw an error if metadata doesn't present SingleLogoutService URL", async (t) => {
|
|
try {
|
|
await logoutController.createRequest({
|
|
...body,
|
|
});
|
|
} catch (err: any) {
|
|
t.equal(err.message, `accounts.google.com doesn't support SLO or disabled by IdP.`);
|
|
t.equal(err.statusCode, 400);
|
|
}
|
|
});
|
|
|
|
t.test('Should return logoutUrl and logoutForm for a valid logout request', async (t) => {
|
|
const result = await logoutController.createRequest({
|
|
...body,
|
|
tenant: 'example.com',
|
|
});
|
|
|
|
t.ok('logoutUrl' in result);
|
|
t.ok('logoutForm' in result);
|
|
t.ok(result.logoutUrl?.includes('https://dev-tyj7qyzz.auth0.com/samlp/logout'));
|
|
|
|
const params = new URLSearchParams(new URL(result.logoutUrl as string).search);
|
|
|
|
t.ok(params.has('SAMLRequest'));
|
|
t.ok(params.has('RelayState'));
|
|
});
|
|
});
|
|
|
|
t.test('handleResponse', async (t) => {
|
|
const sessionId = 'a0089b303b86a97080ff';
|
|
const relayState = `${relayStatePrefix}${sessionId}`;
|
|
|
|
const logoutResponseXML = await fs.readFile(path.join(__dirname, '/data/logout_response.xml'), 'utf8');
|
|
const logoutResponseFailedXML = await fs.readFile(
|
|
path.join(__dirname, '/data/logout_response_failed.xml'),
|
|
'utf8'
|
|
);
|
|
|
|
const logoutResponse = Buffer.from(logoutResponseXML).toString('base64');
|
|
const logoutResponseFailed = Buffer.from(logoutResponseFailedXML).toString('base64');
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
const stubRandomBytes = sinon.stub(crypto, 'randomBytes').returns(sessionId);
|
|
|
|
await logoutController.createRequest({
|
|
...body,
|
|
tenant: 'example.com',
|
|
});
|
|
|
|
t.test('Should throw an error is RelayState is invalid', async (t) => {
|
|
try {
|
|
await logoutController.handleResponse({
|
|
SAMLResponse: logoutResponse,
|
|
RelayState: 'invalid',
|
|
});
|
|
} catch (err: any) {
|
|
t.equal(err.message, 'Unable to validate state from the origin request.');
|
|
t.equal(err.statusCode, 403);
|
|
}
|
|
});
|
|
|
|
t.test('Should throw an error is logout request not success', async (t) => {
|
|
try {
|
|
await logoutController.handleResponse({
|
|
SAMLResponse: logoutResponseFailed,
|
|
RelayState: relayState,
|
|
});
|
|
} catch (err: any) {
|
|
t.equal(err.message, 'SLO failed with status urn:oasis:names:tc:SAML:2.0:status:AuthnFailed.');
|
|
t.equal(err.statusCode, 400);
|
|
}
|
|
});
|
|
|
|
t.test('Should throw an error when request ID mismatch', async (t) => {
|
|
const logoutResponse = Buffer.from(logoutResponseXML.replace(`_${sessionId}`, '_123')).toString(
|
|
'base64'
|
|
);
|
|
|
|
try {
|
|
await logoutController.handleResponse({
|
|
SAMLResponse: logoutResponse,
|
|
RelayState: relayState,
|
|
});
|
|
} catch (err: any) {
|
|
t.equal(err.message, 'SLO failed with mismatched request ID.');
|
|
t.equal(err.statusCode, 400);
|
|
}
|
|
});
|
|
|
|
t.test('Return the redirectUrl after the post logout', async (t) => {
|
|
const result = await logoutController.handleResponse({
|
|
SAMLResponse: logoutResponse,
|
|
RelayState: relayState,
|
|
});
|
|
|
|
t.ok('redirectUrl' in result);
|
|
t.match(result.redirectUrl, saml_connection.defaultRedirectUrl);
|
|
});
|
|
});
|
|
});
|