Files
kami-parse-server/spec/Adapters/Auth/gcenter.spec.js

221 lines
8.7 KiB
JavaScript

const GameCenterAuth = require('../../../lib/Adapters/Auth/gcenter').default;
const { pki } = require('node-forge');
const fs = require('fs');
const path = require('path');
describe('GameCenterAuth Adapter', function () {
let adapter;
beforeEach(function () {
adapter = new GameCenterAuth.constructor();
const gcProd4 = fs.readFileSync(path.resolve(__dirname, '../../support/cert/gc-prod-4.cer'));
const digicertPem = fs.readFileSync(path.resolve(__dirname, '../../support/cert/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem')).toString();
mockFetch([
{
url: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
method: 'GET',
response: {
ok: true,
headers: new Map(),
arrayBuffer: () => Promise.resolve(
gcProd4.buffer.slice(gcProd4.byteOffset, gcProd4.byteOffset + gcProd4.length)
),
},
},
{
url: 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
method: 'GET',
response: {
ok: true,
headers: new Map([['content-type', 'application/x-pem-file'], ['content-length', digicertPem.length.toString()]]),
text: () => Promise.resolve(digicertPem),
},
}
]);
});
describe('Test config failing due to missing params or wrong types', function () {
it('should throw error for invalid options', async function () {
const invalidOptions = [
null,
undefined,
{},
{ bundleId: '' },
{ enableInsecureAuth: false }, // Missing bundleId in secure mode
];
for (const options of invalidOptions) {
expect(() => adapter.validateOptions(options)).withContext(JSON.stringify(options)).toThrow()
}
});
it('should validate options successfully with valid parameters', function () {
const validOptions = { bundleId: 'com.valid.app', enableInsecureAuth: false };
expect(() => adapter.validateOptions(validOptions)).not.toThrow();
});
});
describe('Test payload failing due to missing params or wrong types', function () {
it('should throw error for missing authData fields', async function () {
await expectAsync(adapter.validateAuthData({})).toBeRejectedWithError(
'AuthData id is missing.'
);
});
});
describe('Test payload fails due to incorrect appId / certificate', function () {
it('should throw error for invalid publicKeyUrl', async function () {
const invalidPublicKeyUrl = 'https://malicious.url.com/key.cer';
spyOn(adapter, 'fetchCertificate').and.throwError(
new Error('Invalid publicKeyUrl')
);
await expectAsync(
adapter.getAppleCertificate(invalidPublicKeyUrl)
).toBeRejectedWithError('Invalid publicKeyUrl: https://malicious.url.com/key.cer');
});
it('should throw error for invalid signature verification', async function () {
const fakePublicKey = 'invalid-key';
const fakeAuthData = {
id: '1234567',
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
timestamp: 1460981421303,
salt: 'saltST==',
signature: 'invalidSignature',
};
spyOn(adapter, 'getAppleCertificate').and.returnValue(Promise.resolve(fakePublicKey));
spyOn(adapter, 'verifySignature').and.throwError('Invalid signature.');
await expectAsync(adapter.validateAuthData(fakeAuthData)).toBeRejectedWithError(
'Invalid signature.'
);
});
});
describe('Test payload passing', function () {
it('should successfully process valid payload and save auth data', async function () {
const validAuthData = {
id: '1234567',
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
timestamp: 1460981421303,
salt: 'saltST==',
signature: 'validSignature',
bundleId: 'com.valid.app',
};
spyOn(adapter, 'getAppleCertificate').and.returnValue(Promise.resolve('validKey'));
spyOn(adapter, 'verifySignature').and.returnValue(true);
await expectAsync(adapter.validateAuthData(validAuthData)).toBeResolved();
});
});
describe('Certificate and Signature Validation', function () {
it('should fetch and validate Apple certificate', async function () {
const certUrl = 'https://static.gc.apple.com/public-key/gc-prod-4.cer';
const mockCertificate = 'mockCertificate';
spyOn(adapter, 'fetchCertificate').and.returnValue(
Promise.resolve({ certificate: mockCertificate, headers: new Map() })
);
spyOn(pki, 'certificateFromPem').and.returnValue({});
adapter.cache[certUrl] = mockCertificate;
const cert = await adapter.getAppleCertificate(certUrl);
expect(cert).toBe(mockCertificate);
});
it('should verify signature successfully', async function () {
const authData = {
id: 'G:1965586982',
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
timestamp: 1565257031287,
signature:
'uqLBTr9Uex8zCpc1UQ1MIDMitb+HUat2Mah4Kw6AVLSGe0gGNJXlih2i5X+0ZwVY0S9zY2NHWi2gFjmhjt/4kxWGMkupqXX5H/qhE2m7hzox6lZJpH98ZEUbouWRfZX2ZhUlCkAX09oRNi7fI7mWL1/o88MaI/y6k6tLr14JTzmlxgdyhw+QRLxRPA6NuvUlRSJpyJ4aGtNH5/wHdKQWL8nUnFYiYmaY8R7IjzNxPfy8UJTUWmeZvMSgND4u8EjADPsz7ZtZyWAPi8kYcAb6M8k0jwLD3vrYCB8XXyO2RQb/FY2TM4zJuI7PzLlvvgOJXbbfVtHx7Evnm5NYoyzgzw==',
salt: 'DzqqrQ==',
};
adapter.bundleId = 'cloud.xtralife.gamecenterauth';
adapter.enableInsecureAuth = false;
spyOn(adapter, 'verifyPublicKeyIssuer').and.returnValue();
const publicKey = await adapter.getAppleCertificate(authData.publicKeyUrl);
expect(() => adapter.verifySignature(publicKey, authData)).not.toThrow();
});
it('should not use bundle id from authData payload in secure mode', async function () {
const authData = {
id: 'G:1965586982',
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
timestamp: 1565257031287,
signature:
'uqLBTr9Uex8zCpc1UQ1MIDMitb+HUat2Mah4Kw6AVLSGe0gGNJXlih2i5X+0ZwVY0S9zY2NHWi2gFjmhjt/4kxWGMkupqXX5H/qhE2m7hzox6lZJpH98ZEUbouWRfZX2ZhUlCkAX09oRNi7fI7mWL1/o88MaI/y6k6tLr14JTzmlxgdyhw+QRLxRPA6NuvUlRSJpyJ4aGtNH5/wHdKQWL8nUnFYiYmaY8R7IjzNxPfy8UJTUWmeZvMSgND4u8EjADPsz7ZtZyWAPi8kYcAb6M8k0jwLD3vrYCB8XXyO2RQb/FY2TM4zJuI7PzLlvvgOJXbbfVtHx7Evnm5NYoyzgzw==',
salt: 'DzqqrQ==',
bundleId: 'com.example.insecure.app',
};
adapter.bundleId = 'cloud.xtralife.gamecenterauth';
adapter.enableInsecureAuth = false;
spyOn(adapter, 'verifyPublicKeyIssuer').and.returnValue();
const publicKey = await adapter.getAppleCertificate(authData.publicKeyUrl);
expect(() => adapter.verifySignature(publicKey, authData)).not.toThrow();
});
it('should not use bundle id from authData payload in insecure mode', async function () {
const authData = {
id: 'G:1965586982',
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
timestamp: 1565257031287,
signature:
'uqLBTr9Uex8zCpc1UQ1MIDMitb+HUat2Mah4Kw6AVLSGe0gGNJXlih2i5X+0ZwVY0S9zY2NHWi2gFjmhjt/4kxWGMkupqXX5H/qhE2m7hzox6lZJpH98ZEUbouWRfZX2ZhUlCkAX09oRNi7fI7mWL1/o88MaI/y6k6tLr14JTzmlxgdyhw+QRLxRPA6NuvUlRSJpyJ4aGtNH5/wHdKQWL8nUnFYiYmaY8R7IjzNxPfy8UJTUWmeZvMSgND4u8EjADPsz7ZtZyWAPi8kYcAb6M8k0jwLD3vrYCB8XXyO2RQb/FY2TM4zJuI7PzLlvvgOJXbbfVtHx7Evnm5NYoyzgzw==',
salt: 'DzqqrQ==',
bundleId: 'com.example.insecure.app',
};
adapter.bundleId = 'cloud.xtralife.gamecenterauth';
adapter.enableInsecureAuth = true;
spyOn(adapter, 'verifyPublicKeyIssuer').and.returnValue();
const publicKey = await adapter.getAppleCertificate(authData.publicKeyUrl);
expect(() => adapter.verifySignature(publicKey, authData)).not.toThrow();
});
it('can use bundle id from authData payload in insecure mode', async function () {
const authData = {
id: 'G:1965586982',
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
timestamp: 1565257031287,
signature:
'uqLBTr9Uex8zCpc1UQ1MIDMitb+HUat2Mah4Kw6AVLSGe0gGNJXlih2i5X+0ZwVY0S9zY2NHWi2gFjmhjt/4kxWGMkupqXX5H/qhE2m7hzox6lZJpH98ZEUbouWRfZX2ZhUlCkAX09oRNi7fI7mWL1/o88MaI/y6k6tLr14JTzmlxgdyhw+QRLxRPA6NuvUlRSJpyJ4aGtNH5/wHdKQWL8nUnFYiYmaY8R7IjzNxPfy8UJTUWmeZvMSgND4u8EjADPsz7ZtZyWAPi8kYcAb6M8k0jwLD3vrYCB8XXyO2RQb/FY2TM4zJuI7PzLlvvgOJXbbfVtHx7Evnm5NYoyzgzw==',
salt: 'DzqqrQ==',
bundleId: 'cloud.xtralife.gamecenterauth',
};
adapter.enableInsecureAuth = true;
spyOn(adapter, 'verifyPublicKeyIssuer').and.returnValue();
const publicKey = await adapter.getAppleCertificate(authData.publicKeyUrl);
expect(() => adapter.verifySignature(publicKey, authData)).not.toThrow();
});
});
});