diff --git a/src/script/spec/esi_client.spec.ts b/src/script/spec/esi_client.spec.ts index a24c08b..baa1e34 100644 --- a/src/script/spec/esi_client.spec.ts +++ b/src/script/spec/esi_client.spec.ts @@ -61,6 +61,65 @@ describe('EsiClient', () => { }); }); + describe('.parseToken', () => { + beforeEach(() => { + global.PropertiesService.getScriptProperties = jest.fn().mockReturnValueOnce( + createMock({ + getProperty(): string { + return 'CLIENT_ID'; + }, + }), + ); + }); + + it('with valid token', () => { + global.Utilities.newBlob = jest.fn().mockReturnValueOnce( + createMock({ + getDataAsString(): string { + return '{"aud": ["CLIENT_ID", "EVE Online"], "azp": "CLIENT_ID", "iss": "login.eveonline.com"}'; + }, + }), + ); + + expect(ESIClient.parseToken('HEADER.BODY.SIG')).toEqual({ + aud: ['CLIENT_ID', 'EVE Online'], + azp: 'CLIENT_ID', + iss: 'login.eveonline.com', + }); + expect(global.Utilities.base64DecodeWebSafe).toHaveBeenCalledWith('BODY'); + }); + + it('with new issuer', () => { + global.Utilities.newBlob = jest.fn().mockReturnValueOnce( + createMock({ + getDataAsString(): string { + return '{"aud": ["CLIENT_ID", "EVE Online"], "azp": "CLIENT_ID", "iss": "https://login.eveonline.com"}'; + }, + }), + ); + + expect(ESIClient.parseToken('HEADER.BODY.SIG')).toEqual({ + aud: ['CLIENT_ID', 'EVE Online'], + azp: 'CLIENT_ID', + iss: 'https://login.eveonline.com', + }); + expect(global.Utilities.base64DecodeWebSafe).toHaveBeenCalledWith('BODY'); + }); + + it('with invalid issuer', () => { + global.Utilities.newBlob = jest.fn().mockReturnValueOnce( + createMock({ + getDataAsString(): string { + return '{"aud": ["CLIENT_ID", "EVE Online"], "azp": "CLIENT_ID", "iss": "login.eveonline.net"}'; + }, + }), + ); + + expect(() => ESIClient.parseToken('HEADER.BODY.SIG')).toThrow('Access token validation error: invalid issuer'); + expect(global.Utilities.base64DecodeWebSafe).toHaveBeenCalledWith('BODY'); + }); + }); + describe('#setFunction', () => { beforeEach(() => { endpointProvider.hasEndpoint = jest.fn().mockReturnValueOnce(false); diff --git a/src/script/spec/index.ts b/src/script/spec/index.ts index 388dd68..4567764 100644 --- a/src/script/spec/index.ts +++ b/src/script/spec/index.ts @@ -1 +1,6 @@ import 'jest-ts-auto-mock'; +import { createMock } from 'ts-auto-mock'; + +// Mock AppScript types +global.Utilities = createMock(); +global.PropertiesService = createMock(); diff --git a/src/script/src/esi_client.ts b/src/script/src/esi_client.ts index 35caff1..aaa8993 100644 --- a/src/script/src/esi_client.ts +++ b/src/script/src/esi_client.ts @@ -17,8 +17,10 @@ interface IHTTPClient { class ESIClient { private static readonly BASE_URL = 'https://esi.evetech.net'; private static readonly AUDIENCE = 'EVE Online'; - private static readonly ISSUER = 'login.eveonline.com'; - private static readonly ISSUER_URL_SCHEMA = 'https://login.eveonline.com'; + private static readonly ISSUERS = [ + 'login.eveonline.com', + 'https://login.eveonline.com' + ]; public static addQueryParam(path: string, paramName: string, paramValue: any): string { path += path.includes('?') ? '&' : '?'; @@ -30,7 +32,7 @@ class ESIClient { const jwtToken: IAccessTokenData = JSON.parse(Utilities.newBlob(Utilities.base64DecodeWebSafe(access_token.split('.')[1])).getDataAsString()); const clientId: string = getScriptProperties_().getProperty('CLIENT_ID')!; - if (jwtToken.iss !== ESIClient.ISSUER && jwtToken.iss !== ESIClient.ISSUER_URL_SCHEMA) throw 'Access token validation error: invalid issuer'; + if (!ESIClient.ISSUERS.includes(jwtToken.iss)) throw 'Access token validation error: invalid issuer'; if (jwtToken.aud[0] !== clientId || jwtToken.aud[1] !== ESIClient.AUDIENCE) throw 'Access token validation error: invalid audience'; if (jwtToken.azp !== clientId) throw 'Access token validation error: invalid authorized party'; return jwtToken;