fix: Authentication provider credentials are usable across Parse Server apps; fixes security vulnerability [GHSA-837q-jhwx-cmpv](https://github.com/parse-community/parse-server/security/advisories/GHSA-837q-jhwx-cmpv) (#9667)
This commit is contained in:
@@ -3,99 +3,8 @@ const Config = require('../lib/Config');
|
||||
const defaultColumns = require('../lib/Controllers/SchemaController').defaultColumns;
|
||||
const authenticationLoader = require('../lib/Adapters/Auth');
|
||||
const path = require('path');
|
||||
const responses = {
|
||||
gpgames: { playerId: 'userId' },
|
||||
instagram: { id: 'userId' },
|
||||
janrainengage: { stat: 'ok', profile: { identifier: 'userId' } },
|
||||
janraincapture: { stat: 'ok', result: 'userId' },
|
||||
line: { userId: 'userId' },
|
||||
vkontakte: { response: [{ id: 'userId' }] },
|
||||
google: { sub: 'userId' },
|
||||
wechat: { errcode: 0 },
|
||||
weibo: { uid: 'userId' },
|
||||
qq: 'callback( {"openid":"userId"} );', // yes it's like that, run eval in the client :P
|
||||
phantauth: { sub: 'userId' },
|
||||
microsoft: { id: 'userId', mail: 'userMail' },
|
||||
};
|
||||
|
||||
describe('AuthenticationProviders', function () {
|
||||
[
|
||||
'apple',
|
||||
'gcenter',
|
||||
'gpgames',
|
||||
'facebook',
|
||||
'github',
|
||||
'instagram',
|
||||
'google',
|
||||
'linkedin',
|
||||
'meetup',
|
||||
'twitter',
|
||||
'janrainengage',
|
||||
'janraincapture',
|
||||
'line',
|
||||
'vkontakte',
|
||||
'qq',
|
||||
'spotify',
|
||||
'wechat',
|
||||
'weibo',
|
||||
'phantauth',
|
||||
'microsoft',
|
||||
'keycloak',
|
||||
].map(function (providerName) {
|
||||
it('Should validate structure of ' + providerName, done => {
|
||||
const provider = require('../lib/Adapters/Auth/' + providerName);
|
||||
jequal(typeof provider.validateAuthData, 'function');
|
||||
jequal(typeof provider.validateAppId, 'function');
|
||||
const validateAuthDataPromise = provider.validateAuthData({}, {});
|
||||
const validateAppIdPromise = provider.validateAppId('app', 'key', {});
|
||||
jequal(validateAuthDataPromise.constructor, Promise.prototype.constructor);
|
||||
jequal(validateAppIdPromise.constructor, Promise.prototype.constructor);
|
||||
validateAuthDataPromise.then(
|
||||
() => {},
|
||||
() => {}
|
||||
);
|
||||
validateAppIdPromise.then(
|
||||
() => {},
|
||||
() => {}
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it(`should provide the right responses for adapter ${providerName}`, async () => {
|
||||
const noResponse = ['twitter', 'apple', 'gcenter', 'google', 'keycloak'];
|
||||
if (noResponse.includes(providerName)) {
|
||||
return;
|
||||
}
|
||||
spyOn(require('../lib/Adapters/Auth/httpsRequest'), 'get').and.callFake(options => {
|
||||
if (
|
||||
options ===
|
||||
'https://oauth.vk.com/access_token?client_id=appId&client_secret=appSecret&v=5.123&grant_type=client_credentials' ||
|
||||
options ===
|
||||
'https://oauth.vk.com/access_token?client_id=appId&client_secret=appSecret&v=5.124&grant_type=client_credentials'
|
||||
) {
|
||||
return {
|
||||
access_token: 'access_token',
|
||||
};
|
||||
}
|
||||
return Promise.resolve(responses[providerName] || { id: 'userId' });
|
||||
});
|
||||
spyOn(require('../lib/Adapters/Auth/httpsRequest'), 'request').and.callFake(() => {
|
||||
return Promise.resolve(responses[providerName] || { id: 'userId' });
|
||||
});
|
||||
const provider = require('../lib/Adapters/Auth/' + providerName);
|
||||
let params = {};
|
||||
if (providerName === 'vkontakte') {
|
||||
params = {
|
||||
appIds: 'appId',
|
||||
appSecret: 'appSecret',
|
||||
};
|
||||
await provider.validateAuthData({ id: 'userId' }, params);
|
||||
params.appVersion = '5.123';
|
||||
}
|
||||
await provider.validateAuthData({ id: 'userId' }, params);
|
||||
});
|
||||
});
|
||||
|
||||
const getMockMyOauthProvider = function () {
|
||||
return {
|
||||
authData: {
|
||||
@@ -568,46 +477,6 @@ describe('AuthenticationProviders', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('instagram auth adapter', () => {
|
||||
const instagram = require('../lib/Adapters/Auth/instagram');
|
||||
const httpsRequest = require('../lib/Adapters/Auth/httpsRequest');
|
||||
|
||||
it('should use default api', async () => {
|
||||
spyOn(httpsRequest, 'get').and.callFake(() => {
|
||||
return Promise.resolve({ data: { id: 'userId' } });
|
||||
});
|
||||
await instagram.validateAuthData({ id: 'userId', access_token: 'the_token' }, {});
|
||||
expect(httpsRequest.get).toHaveBeenCalledWith(
|
||||
'https://graph.instagram.com/me?fields=id&access_token=the_token'
|
||||
);
|
||||
});
|
||||
it('response object without data child', async () => {
|
||||
spyOn(httpsRequest, 'get').and.callFake(() => {
|
||||
return Promise.resolve({ id: 'userId' });
|
||||
});
|
||||
await instagram.validateAuthData({ id: 'userId', access_token: 'the_token' }, {});
|
||||
expect(httpsRequest.get).toHaveBeenCalledWith(
|
||||
'https://graph.instagram.com/me?fields=id&access_token=the_token'
|
||||
);
|
||||
});
|
||||
it('should pass in api url', async () => {
|
||||
spyOn(httpsRequest, 'get').and.callFake(() => {
|
||||
return Promise.resolve({ data: { id: 'userId' } });
|
||||
});
|
||||
await instagram.validateAuthData(
|
||||
{
|
||||
id: 'userId',
|
||||
access_token: 'the_token',
|
||||
apiURL: 'https://new-api.instagram.com/v1/',
|
||||
},
|
||||
{}
|
||||
);
|
||||
expect(httpsRequest.get).toHaveBeenCalledWith(
|
||||
'https://new-api.instagram.com/v1/me?fields=id&access_token=the_token'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('google auth adapter', () => {
|
||||
const google = require('../lib/Adapters/Auth/google');
|
||||
const jwt = require('jsonwebtoken');
|
||||
@@ -730,35 +599,6 @@ describe('google auth adapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('google play games service auth', () => {
|
||||
const gpgames = require('../lib/Adapters/Auth/gpgames');
|
||||
const httpsRequest = require('../lib/Adapters/Auth/httpsRequest');
|
||||
|
||||
it('validateAuthData should pass validation', async () => {
|
||||
spyOn(httpsRequest, 'get').and.callFake(() => {
|
||||
return Promise.resolve({ playerId: 'userId' });
|
||||
});
|
||||
await gpgames.validateAuthData({
|
||||
id: 'userId',
|
||||
access_token: 'access_token',
|
||||
});
|
||||
});
|
||||
|
||||
it('validateAuthData should throw error', async () => {
|
||||
spyOn(httpsRequest, 'get').and.callFake(() => {
|
||||
return Promise.resolve({ playerId: 'invalid' });
|
||||
});
|
||||
try {
|
||||
await gpgames.validateAuthData({
|
||||
id: 'userId',
|
||||
access_token: 'access_token',
|
||||
});
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Google Play Games Services - authData is invalid for this user.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('keycloak auth adapter', () => {
|
||||
const keycloak = require('../lib/Adapters/Auth/keycloak');
|
||||
const httpsRequest = require('../lib/Adapters/Auth/httpsRequest');
|
||||
@@ -987,433 +827,6 @@ describe('keycloak auth adapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('oauth2 auth adapter', () => {
|
||||
const oauth2 = require('../lib/Adapters/Auth/oauth2');
|
||||
const httpsRequest = require('../lib/Adapters/Auth/httpsRequest');
|
||||
|
||||
it('properly loads OAuth2 adapter via the "oauth2" option', () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
},
|
||||
};
|
||||
const loadedAuthAdapter = authenticationLoader.loadAuthAdapter('oauth2Authentication', options);
|
||||
expect(loadedAuthAdapter.adapter).toEqual(oauth2);
|
||||
});
|
||||
|
||||
it('properly loads OAuth2 adapter with options', () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
useridField: 'sub',
|
||||
appidField: 'appId',
|
||||
appIds: ['a', 'b'],
|
||||
authorizationHeader: 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=',
|
||||
debug: true,
|
||||
},
|
||||
};
|
||||
const loadedAuthAdapter = authenticationLoader.loadAuthAdapter('oauth2Authentication', options);
|
||||
const appIds = loadedAuthAdapter.appIds;
|
||||
const providerOptions = loadedAuthAdapter.providerOptions;
|
||||
expect(providerOptions.tokenIntrospectionEndpointUrl).toEqual('https://example.com/introspect');
|
||||
expect(providerOptions.useridField).toEqual('sub');
|
||||
expect(providerOptions.appidField).toEqual('appId');
|
||||
expect(appIds).toEqual(['a', 'b']);
|
||||
expect(providerOptions.authorizationHeader).toEqual('Basic dXNlcm5hbWU6cGFzc3dvcmQ=');
|
||||
expect(providerOptions.debug).toEqual(true);
|
||||
});
|
||||
|
||||
it('validateAppId should fail if OAuth2 tokenIntrospectionEndpointUrl is not configured properly', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
appIds: ['a', 'b'],
|
||||
appidField: 'appId',
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
try {
|
||||
await adapter.validateAppId(appIds, authData, providerOptions);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'OAuth2 token introspection endpoint URL is missing from configuration!'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAppId appidField optional', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
try {
|
||||
await adapter.validateAppId(appIds, authData, providerOptions);
|
||||
} catch (e) {
|
||||
// Should not reach here
|
||||
fail(e);
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAppId should fail without appIds', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
appidField: 'appId',
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
try {
|
||||
await adapter.validateAppId(appIds, authData, providerOptions);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'OAuth2 configuration is missing the client app IDs ("appIds" config parameter).'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAppId should fail empty appIds', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
appidField: 'appId',
|
||||
appIds: [],
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
try {
|
||||
await adapter.validateAppId(appIds, authData, providerOptions);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'OAuth2 configuration is missing the client app IDs ("appIds" config parameter).'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAppId invalid accessToken', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
appidField: 'appId',
|
||||
appIds: ['a', 'b'],
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
spyOn(httpsRequest, 'request').and.callFake(() => {
|
||||
return Promise.resolve({});
|
||||
});
|
||||
try {
|
||||
await adapter.validateAppId(appIds, authData, providerOptions);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('OAuth2 access token is invalid for this user.');
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAppId invalid accessToken appId', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
appidField: 'appId',
|
||||
appIds: ['a', 'b'],
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
spyOn(httpsRequest, 'request').and.callFake(() => {
|
||||
return Promise.resolve({ active: true });
|
||||
});
|
||||
try {
|
||||
await adapter.validateAppId(appIds, authData, providerOptions);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
"OAuth2: the access_token's appID is empty or is not in the list of permitted appIDs in the auth configuration."
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAppId valid accessToken appId', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
appidField: 'appId',
|
||||
appIds: ['a', 'b'],
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
spyOn(httpsRequest, 'request').and.callFake(() => {
|
||||
return Promise.resolve({
|
||||
active: true,
|
||||
appId: 'a',
|
||||
});
|
||||
});
|
||||
try {
|
||||
await adapter.validateAppId(appIds, authData, providerOptions);
|
||||
} catch (e) {
|
||||
// Should not enter here
|
||||
fail(e);
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAppId valid accessToken appId array', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
appidField: 'appId',
|
||||
appIds: ['a', 'b'],
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
spyOn(httpsRequest, 'request').and.callFake(() => {
|
||||
return Promise.resolve({
|
||||
active: true,
|
||||
appId: ['a'],
|
||||
});
|
||||
});
|
||||
try {
|
||||
await adapter.validateAppId(appIds, authData, providerOptions);
|
||||
} catch (e) {
|
||||
// Should not enter here
|
||||
fail(e);
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAppId valid accessToken invalid appId', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
appidField: 'appId',
|
||||
appIds: ['a', 'b'],
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
spyOn(httpsRequest, 'request').and.callFake(() => {
|
||||
return Promise.resolve({
|
||||
active: true,
|
||||
appId: 'unknown',
|
||||
});
|
||||
});
|
||||
try {
|
||||
await adapter.validateAppId(appIds, authData, providerOptions);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
"OAuth2: the access_token's appID is empty or is not in the list of permitted appIDs in the auth configuration."
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAuthData should fail if OAuth2 tokenIntrospectionEndpointUrl is not configured properly', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
try {
|
||||
await adapter.validateAuthData(authData, providerOptions);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'OAuth2 token introspection endpoint URL is missing from configuration!'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAuthData invalid accessToken', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
useridField: 'sub',
|
||||
appidField: 'appId',
|
||||
appIds: ['a', 'b'],
|
||||
authorizationHeader: 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=',
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
spyOn(httpsRequest, 'request').and.callFake(() => {
|
||||
return Promise.resolve({});
|
||||
});
|
||||
try {
|
||||
await adapter.validateAuthData(authData, providerOptions);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('OAuth2 access token is invalid for this user.');
|
||||
}
|
||||
expect(httpsRequest.request).toHaveBeenCalledWith(
|
||||
{
|
||||
hostname: 'example.com',
|
||||
path: '/introspect',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': 15,
|
||||
Authorization: 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=',
|
||||
},
|
||||
},
|
||||
'token=sometoken'
|
||||
);
|
||||
});
|
||||
|
||||
it('validateAuthData valid accessToken', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
useridField: 'sub',
|
||||
appidField: 'appId',
|
||||
appIds: ['a', 'b'],
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
spyOn(httpsRequest, 'request').and.callFake(() => {
|
||||
return Promise.resolve({
|
||||
active: true,
|
||||
sub: 'fakeid',
|
||||
});
|
||||
});
|
||||
try {
|
||||
await adapter.validateAuthData(authData, providerOptions);
|
||||
} catch (e) {
|
||||
// Should not enter here
|
||||
fail(e);
|
||||
}
|
||||
expect(httpsRequest.request).toHaveBeenCalledWith(
|
||||
{
|
||||
hostname: 'example.com',
|
||||
path: '/introspect',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': 15,
|
||||
},
|
||||
},
|
||||
'token=sometoken'
|
||||
);
|
||||
});
|
||||
|
||||
it('validateAuthData valid accessToken without useridField', async () => {
|
||||
const options = {
|
||||
oauth2Authentication: {
|
||||
oauth2: true,
|
||||
tokenIntrospectionEndpointUrl: 'https://example.com/introspect',
|
||||
appidField: 'appId',
|
||||
appIds: ['a', 'b'],
|
||||
},
|
||||
};
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
};
|
||||
const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'oauth2Authentication',
|
||||
options
|
||||
);
|
||||
spyOn(httpsRequest, 'request').and.callFake(() => {
|
||||
return Promise.resolve({
|
||||
active: true,
|
||||
sub: 'fakeid',
|
||||
});
|
||||
});
|
||||
try {
|
||||
await adapter.validateAuthData(authData, providerOptions);
|
||||
} catch (e) {
|
||||
// Should not enter here
|
||||
fail(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('apple signin auth adapter', () => {
|
||||
const apple = require('../lib/Adapters/Auth/apple');
|
||||
const jwt = require('jsonwebtoken');
|
||||
@@ -1722,206 +1135,17 @@ describe('apple signin auth adapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Apple Game Center Auth adapter', () => {
|
||||
const gcenter = require('../lib/Adapters/Auth/gcenter');
|
||||
const fs = require('fs');
|
||||
const testCert = fs.readFileSync(__dirname + '/support/cert/game_center.pem');
|
||||
const testCert2 = fs.readFileSync(__dirname + '/support/cert/game_center.pem');
|
||||
|
||||
it('can load adapter', async () => {
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl:
|
||||
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
});
|
||||
|
||||
it('validateAuthData should validate', async () => {
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl:
|
||||
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
// real token is used
|
||||
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',
|
||||
};
|
||||
gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-4.cer'] = testCert;
|
||||
await gcenter.validateAuthData(authData);
|
||||
});
|
||||
|
||||
it('validateAuthData invalid signature id', async () => {
|
||||
gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-4.cer'] = testCert;
|
||||
gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-6.cer'] = testCert2;
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
{}
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
const authData = {
|
||||
id: 'G:1965586982',
|
||||
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-6.cer',
|
||||
timestamp: 1565257031287,
|
||||
signature: '1234',
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'com.example.com',
|
||||
};
|
||||
await expectAsync(gcenter.validateAuthData(authData)).toBeRejectedWith(
|
||||
new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Apple Game Center - invalid signature')
|
||||
);
|
||||
});
|
||||
|
||||
it('validateAuthData invalid public key http url', async () => {
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl:
|
||||
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
const publicKeyUrls = [
|
||||
'example.com',
|
||||
'http://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
'https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg',
|
||||
'https://example.com/ \\.apple.com/public_key.cer',
|
||||
'https://example.com/ &.apple.com/public_key.cer',
|
||||
];
|
||||
await Promise.all(
|
||||
publicKeyUrls.map(publicKeyUrl =>
|
||||
expectAsync(
|
||||
gcenter.validateAuthData({
|
||||
id: 'G:1965586982',
|
||||
timestamp: 1565257031287,
|
||||
publicKeyUrl,
|
||||
signature: '1234',
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'com.example.com',
|
||||
})
|
||||
).toBeRejectedWith(
|
||||
new Parse.Error(
|
||||
Parse.Error.SCRIPT_FAILED,
|
||||
`Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}`
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should not validate Symantec Cert', async () => {
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl:
|
||||
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
expect(() =>
|
||||
gcenter.verifyPublicKeyIssuer(
|
||||
testCert,
|
||||
'https://static.gc.apple.com/public-key/gc-prod-4.cer'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('adapter should load default cert', async () => {
|
||||
const options = {
|
||||
gcenter: {},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
const previous = new Date();
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
|
||||
const duration = new Date().getTime() - previous.getTime();
|
||||
expect(duration <= 1).toBe(true);
|
||||
});
|
||||
|
||||
it('adapter should throw', async () => {
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl: 'https://example.com',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await expectAsync(
|
||||
adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
)
|
||||
).toBeRejectedWith(
|
||||
new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('phant auth adapter', () => {
|
||||
const httpsRequest = require('../lib/Adapters/Auth/httpsRequest');
|
||||
|
||||
it('validateAuthData should throw for invalid auth', async () => {
|
||||
await reconfigureServer({
|
||||
auth: {
|
||||
phantauth: {
|
||||
enableInsecureAuth: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
const authData = {
|
||||
id: 'fakeid',
|
||||
access_token: 'sometoken',
|
||||
@@ -1938,34 +1162,6 @@ describe('phant auth adapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('microsoft graph auth adapter', () => {
|
||||
const microsoft = require('../lib/Adapters/Auth/microsoft');
|
||||
const httpsRequest = require('../lib/Adapters/Auth/httpsRequest');
|
||||
|
||||
it('should use access_token for validation is passed and responds with id and mail', async () => {
|
||||
spyOn(httpsRequest, 'get').and.callFake(() => {
|
||||
return Promise.resolve({ id: 'userId', mail: 'userMail' });
|
||||
});
|
||||
await microsoft.validateAuthData({
|
||||
id: 'userId',
|
||||
access_token: 'the_token',
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to validate Microsoft Graph auth with bad token', done => {
|
||||
const authData = {
|
||||
id: 'fake-id',
|
||||
mail: 'fake@mail.com',
|
||||
access_token: 'very.long.bad.token',
|
||||
};
|
||||
microsoft.validateAuthData(authData).then(done.fail, err => {
|
||||
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
||||
expect(err.message).toBe('Microsoft Graph auth is invalid for this user.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('facebook limited auth adapter', () => {
|
||||
const facebook = require('../lib/Adapters/Auth/facebook');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
Reference in New Issue
Block a user