feat(cloudflare-auth-access-provider): Add support for cloudflare custom headers and cookie auth name
Signed-off-by: Jason Diaz G. <jason.rene.diaz.gonzalez@ericsson.com>
This commit is contained in:
@@ -27,6 +27,8 @@ export interface Config {
|
||||
token: string;
|
||||
subject: string;
|
||||
}>;
|
||||
customHeader?: string;
|
||||
customCookieAuthName?: string;
|
||||
};
|
||||
/**
|
||||
* The backstage token expiration.
|
||||
|
||||
@@ -239,4 +239,83 @@ describe('helpers', () => {
|
||||
token: token,
|
||||
});
|
||||
});
|
||||
|
||||
it('works for regular tokens, through custom header auth', async () => {
|
||||
jest.useFakeTimers({
|
||||
now: 1600000004000,
|
||||
});
|
||||
|
||||
const helper = AuthHelper.fromConfig(
|
||||
new ConfigReader({ teamName: 'mock-team', customHeader: 'X-Auth-Token' }),
|
||||
{ cache },
|
||||
);
|
||||
const token = await tokenFactory.userToken();
|
||||
const request = createRequest({
|
||||
headers: { ['X-Auth-Token']: token },
|
||||
});
|
||||
|
||||
const expected = {
|
||||
cfIdentity: {
|
||||
email: 'hello@example.com',
|
||||
groups: [{ id: '123', email: 'foo@bar.com', name: 'foo' }],
|
||||
id: '1234567890',
|
||||
name: 'User Name',
|
||||
},
|
||||
claims: {
|
||||
iss: `https://mock-team.cloudflareaccess.com`,
|
||||
sub: '1234567890',
|
||||
name: 'User Name',
|
||||
iat: 1600000000,
|
||||
exp: 1600000005,
|
||||
},
|
||||
expiresInSeconds: 5,
|
||||
};
|
||||
|
||||
await expect(helper.authenticate(request)).resolves.toEqual({
|
||||
...expected,
|
||||
token: token,
|
||||
});
|
||||
expect(cache.set).toHaveBeenCalledTimes(1);
|
||||
expect(cache.set.mock.calls[0][0]).toBe(
|
||||
'providers/cloudflare-access/profile-v1/1234567890',
|
||||
);
|
||||
expect(JSON.parse(cache.set.mock.calls[0][1] as string)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('works for regular tokens, through custom cookie auth name', async () => {
|
||||
jest.useFakeTimers({
|
||||
now: 1600000004000,
|
||||
});
|
||||
|
||||
const helper = AuthHelper.fromConfig(
|
||||
new ConfigReader({ teamName: 'mock-team', customCookieAuthName: 'CF_Custom_Auth' }),
|
||||
{ cache },
|
||||
);
|
||||
const token = await tokenFactory.userToken();
|
||||
const request = createRequest({
|
||||
cookies: { 'CF_Custom_Auth': token },
|
||||
});
|
||||
|
||||
const expected = {
|
||||
cfIdentity: {
|
||||
email: 'hello@example.com',
|
||||
groups: [{ id: '123', email: 'foo@bar.com', name: 'foo' }],
|
||||
id: '1234567890',
|
||||
name: 'User Name',
|
||||
},
|
||||
claims: {
|
||||
iss: `https://mock-team.cloudflareaccess.com`,
|
||||
sub: '1234567890',
|
||||
name: 'User Name',
|
||||
iat: 1600000000,
|
||||
exp: 1600000005,
|
||||
},
|
||||
expiresInSeconds: 5,
|
||||
};
|
||||
|
||||
await expect(helper.authenticate(request)).resolves.toEqual({
|
||||
...expected,
|
||||
token: token,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,6 +40,8 @@ export class AuthHelper {
|
||||
options?: { cache?: CacheService },
|
||||
): AuthHelper {
|
||||
const teamName = config.getString('teamName');
|
||||
const customHeader = config.getString('customHeader');
|
||||
const customCookieAuthName = config.getString('customCookieAuthName');
|
||||
const serviceTokens = (
|
||||
config.getOptionalConfigArray('serviceTokens') ?? []
|
||||
)?.map(cfg => {
|
||||
@@ -53,7 +55,7 @@ export class AuthHelper {
|
||||
new URL(`https://${teamName}.cloudflareaccess.com/cdn-cgi/access/certs`),
|
||||
);
|
||||
|
||||
return new AuthHelper(teamName, serviceTokens, keySet, options?.cache);
|
||||
return new AuthHelper(teamName, serviceTokens, keySet, options?.cache, customHeader, customCookieAuthName);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
@@ -61,20 +63,23 @@ export class AuthHelper {
|
||||
private readonly serviceTokens: ServiceToken[],
|
||||
private readonly keySet: ReturnType<typeof createRemoteJWKSet>,
|
||||
private readonly cache?: CacheService,
|
||||
) {}
|
||||
private readonly customHeader?: string,
|
||||
private readonly customCookieAuthName?: string,
|
||||
) { }
|
||||
|
||||
async authenticate(req: express.Request): Promise<CloudflareAccessResult> {
|
||||
// JWTs generated by Access are available in a request header as
|
||||
// Cf-Access-Jwt-Assertion and as cookies as CF_Authorization.
|
||||
let jwt = req.header(CF_JWT_HEADER);
|
||||
let jwt = req.header(this.customHeader || CF_JWT_HEADER);
|
||||
if (!jwt) {
|
||||
jwt = req.cookies.CF_Authorization;
|
||||
jwt = req.cookies[this.customCookieAuthName || COOKIE_AUTH_NAME];
|
||||
}
|
||||
if (!jwt) {
|
||||
// Only throw if both are not provided by Cloudflare Access since either
|
||||
// can be used.
|
||||
throw new AuthenticationError(
|
||||
`Missing ${CF_JWT_HEADER} from Cloudflare Access`,
|
||||
`Missing ${this.customHeader || CF_JWT_HEADER} and
|
||||
${this.customCookieAuthName || COOKIE_AUTH_NAME} from Cloudflare Access`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -155,8 +160,8 @@ export class AuthHelper {
|
||||
): Promise<CloudflareAccessIdentityProfile> {
|
||||
const headers = new Headers();
|
||||
// set both headers just the way inbound responses are set
|
||||
headers.set(CF_JWT_HEADER, jwt);
|
||||
headers.set('cookie', `${COOKIE_AUTH_NAME}=${jwt}`);
|
||||
headers.set(this.customHeader || CF_JWT_HEADER, jwt);
|
||||
headers.set('cookie', `${this.customCookieAuthName || COOKIE_AUTH_NAME}=${jwt}`);
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/get-identity`,
|
||||
|
||||
Reference in New Issue
Block a user