Skip to content

Commit

Permalink
Merge pull request #705 from Sanofi-IADC/feature/multi_auth
Browse files Browse the repository at this point in the history
feat(auth): add proxy to jwks retrieval
  • Loading branch information
alastasWow authored Mar 3, 2022
2 parents 208ccf2 + e6fb3da commit cb3e6a6
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 10 deletions.
5 changes: 3 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"graphql-type-json": "0.3.2",
"graphql-upload": "13.0.0",
"haiku-random": "1.0.0",
"https-proxy-agent": "^5.0.0",
"ioredis": "4.28.2",
"joi": "17.5.0",
"jwks-rsa": "^2.0.5",
Expand Down
30 changes: 23 additions & 7 deletions src/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import * as fs from 'fs';
import { Agent } from 'http';
import * as tunnel from 'tunnel';
import { ExtractJwt } from '@mestrak/passport-multi-jwt';
import { passportJwtSecret } from 'jwks-rsa';
import { passportJwtSecret, ExpressJwtOptions } from 'jwks-rsa';
import createHttpsProxyAgent from 'https-proxy-agent';
import validationSchema from './environmentValidationSchema';

@Injectable()
Expand Down Expand Up @@ -81,8 +82,12 @@ export class ConfigService {
return options;
}

getProxy(): string | undefined {
return this.get('HTTP_PROXY') || this.get('HTTPS_PROXY');
}

getHttpsTunnel(): Agent | undefined {
const proxy = this.get('HTTP_PROXY') || this.get('HTTPS_PROXY');
const proxy = this.getProxy();
if (!proxy) {
return undefined;
}
Expand All @@ -109,11 +114,22 @@ export class ConfigService {
getAuthConfig(): any {
const authConfig = this.get('AUTH_CONFIG_SECRET');

const proxy = this.getProxy();
let agent: createHttpsProxyAgent.HttpsProxyAgent | null = null;
if (proxy) {
agent = createHttpsProxyAgent(proxy);
}
// patch in ExtractJwt and passportJwtSecret function calls as they cannot be enocded in JSON
return authConfig.config.map((configuration) => ({
...configuration,
...(configuration.secretOrKeyProvider ? { secretOrKeyProvider: passportJwtSecret(configuration.secretOrKeyProvider) } : {}),
jwtFromRequest: ExtractJwt[configuration.jwtFromRequest.funcName](configuration.jwtFromRequest.args),
}));
return authConfig.config.map((configuration) => {
const option: ExpressJwtOptions = { ...configuration.secretOrKeyProvider };
if (proxy) {
option.requestAgent = agent;
}
return {
...configuration,
...(configuration.secretOrKeyProvider ? { secretOrKeyProvider: passportJwtSecret(option) } : {}),
jwtFromRequest: ExtractJwt[configuration.jwtFromRequest.funcName](configuration.jwtFromRequest.args),
};
});
}
}
58 changes: 57 additions & 1 deletion tests/unit/config/config.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Test } from '@nestjs/testing';
import { ExtractJwt } from '@mestrak/passport-multi-jwt';
import { passportJwtSecret } from 'jwks-rsa';
import JwksRsa, { passportJwtSecret } from 'jwks-rsa';
import { AUTH } from '../../testUtils/testingConsts';
import { ConfigService } from '../../../src/config/config.service';

Expand All @@ -12,6 +12,14 @@ async function getConfigService() {
}

describe('ConfigService', () => {
beforeEach(() => {
jest.clearAllMocks();
});

afterEach(() => {
jest.clearAllMocks();
});

it('Correctly configures secretOrKeyProvider', async () => {
// Given AUTH_CONFIG_SECRET environment variable is configured with secretOrKeyProvider
process.env.AUTH_CONFIG_SECRET = AUTH.AUTH_CONFIG_SECRET_JWKS;
Expand Down Expand Up @@ -84,6 +92,54 @@ describe('ConfigService', () => {
}
});

it('Should correctly configure proxy if available', async () => {
// Given AUTH_CONFIG_SECRET environment variable is configured with secretOrKeyProvider
process.env.AUTH_CONFIG_SECRET = AUTH.AUTH_CONFIG_SECRET_JWKS;
process.env.HTTP_PROXY = 'https://proxy_uri:port';
const spy = jest.spyOn(JwksRsa, 'passportJwtSecret');

// When the auth configuration is retrieved
const configService = await getConfigService();
configService.getAuthConfig();

// (passportJwtSecret as jest.Mock).mockReturnValue(() => {});
// Then the configuration should use the jwks passportJwtSecret function
expect(spy).toBeCalledWith(
expect.objectContaining({
requestAgent: expect.objectContaining({
proxy: expect.objectContaining({
protocol: 'https:',
host: 'proxy_uri',
port: 443,
hostname: 'proxy_uri',
href: 'https://proxy_uri/:port',
}),
}),
}),
);
expect(spy).toHaveBeenCalledTimes(1);
});

it('Should correctly configure proxy if not available', async () => {
// Given AUTH_CONFIG_SECRET environment variable is configured with secretOrKeyProvider
process.env.AUTH_CONFIG_SECRET = AUTH.AUTH_CONFIG_SECRET_JWKS;
delete process.env.HTTP_PROXY;
const spy = jest.spyOn(JwksRsa, 'passportJwtSecret');

// When the auth configuration is retrieved
const configService = await getConfigService();
configService.getAuthConfig();

// (passportJwtSecret as jest.Mock).mockReturnValue(() => {});
// Then the configuration should use the jwks passportJwtSecret function
expect(spy).toBeCalledWith(
expect.not.objectContaining({
requestAgent: expect.anything(),
}),
);
expect(spy).toHaveBeenCalledTimes(1);
});

describe('JWT extractors', () => {
it('Should correctly set JWT extraction method jwtFromRequest: fromAuthHeaderAsBearerToken', async () => {
// Given AUTH_CONFIG_SECRET environment variable is configured to extract the JWT using fromAuthHeaderAsBearerToken
Expand Down

0 comments on commit cb3e6a6

Please sign in to comment.