1306 lines
43 KiB
JavaScript
1306 lines
43 KiB
JavaScript
const request = require('../lib/request');
|
|
const Auth = require('../lib/Auth');
|
|
const requestWithExpectedError = async params => {
|
|
try {
|
|
return await request(params);
|
|
} catch (e) {
|
|
throw new Error(e.data.error);
|
|
}
|
|
};
|
|
describe('Auth Adapter features', () => {
|
|
const baseAdapter = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateAuthData: () => Promise.resolve(),
|
|
};
|
|
const baseAdapter2 = {
|
|
validateAppId: appIds => (appIds[0] === 'test' ? Promise.resolve() : Promise.reject()),
|
|
validateAuthData: () => Promise.resolve(),
|
|
appIds: ['test'],
|
|
options: { anOption: true },
|
|
};
|
|
|
|
const doNotSaveAdapter = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateAuthData: () => Promise.resolve({ doNotSave: true }),
|
|
};
|
|
|
|
const additionalAdapter = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateAuthData: () => Promise.resolve(),
|
|
policy: 'additional',
|
|
};
|
|
|
|
const soloAdapter = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateAuthData: () => Promise.resolve(),
|
|
policy: 'solo',
|
|
};
|
|
|
|
const challengeAdapter = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateAuthData: () => Promise.resolve(),
|
|
challenge: () => Promise.resolve({ token: 'test' }),
|
|
options: {
|
|
anOption: true,
|
|
},
|
|
};
|
|
|
|
const modernAdapter = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateSetUp: () => Promise.resolve(),
|
|
validateUpdate: () => Promise.resolve(),
|
|
validateLogin: () => Promise.resolve(),
|
|
};
|
|
|
|
const modernAdapter2 = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateSetUp: () => Promise.resolve(),
|
|
validateUpdate: () => Promise.resolve(),
|
|
validateLogin: () => Promise.resolve(),
|
|
};
|
|
|
|
const modernAdapter3 = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateSetUp: () => Promise.resolve(),
|
|
validateUpdate: () => Promise.resolve(),
|
|
validateLogin: () => Promise.resolve(),
|
|
validateOptions: () => Promise.resolve(),
|
|
afterFind() {
|
|
return {
|
|
foo: 'bar',
|
|
};
|
|
},
|
|
};
|
|
|
|
const wrongAdapter = {
|
|
validateAppId: () => Promise.resolve(),
|
|
};
|
|
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
'X-Parse-Application-Id': 'test',
|
|
'X-Parse-REST-API-Key': 'rest',
|
|
};
|
|
|
|
it('should ensure no duplicate auth data id after before save', async () => {
|
|
await reconfigureServer({
|
|
auth: { baseAdapter },
|
|
cloud: () => {
|
|
Parse.Cloud.beforeSave('_User', async request => {
|
|
request.object.set('authData', { baseAdapter: { id: 'test' } });
|
|
});
|
|
},
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
await user.save({ authData: { baseAdapter: { id: 'another' } } });
|
|
await user.fetch({ useMasterKey: true });
|
|
expect(user.get('authData')).toEqual({ baseAdapter: { id: 'test' } });
|
|
|
|
const user2 = new Parse.User();
|
|
await expectAsync(
|
|
user2.save({ authData: { baseAdapter: { id: 'another' } } })
|
|
).toBeRejectedWithError('this auth is already used');
|
|
});
|
|
|
|
it('should ensure no duplicate auth data id after before save in case of more than one result', async () => {
|
|
await reconfigureServer({
|
|
auth: { baseAdapter },
|
|
cloud: () => {
|
|
Parse.Cloud.beforeSave('_User', async request => {
|
|
request.object.set('authData', { baseAdapter: { id: 'test' } });
|
|
});
|
|
},
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
await user.save({ authData: { baseAdapter: { id: 'another' } } });
|
|
await user.fetch({ useMasterKey: true });
|
|
expect(user.get('authData')).toEqual({ baseAdapter: { id: 'test' } });
|
|
|
|
let i = 0;
|
|
const originalFn = Auth.findUsersWithAuthData;
|
|
spyOn(Auth, 'findUsersWithAuthData').and.callFake((...params) => {
|
|
// First call is triggered during authData validation
|
|
if (i === 0) {
|
|
i++;
|
|
return originalFn(...params);
|
|
}
|
|
// Second call is triggered after beforeSave. A developer can modify authData during beforeSave.
|
|
// To perform a determinist login, the uniqueness of `auth.id` needs to be ensured.
|
|
// A developer with a direct access to the database could break something and duplicate authData.id.
|
|
// In this case, if 2 matching users are detected for a single authData.id, then the login/register will be canceled.
|
|
// Promise.resolve([true, true]) simulates this case with 2 matching users.
|
|
return Promise.resolve([true, true]);
|
|
});
|
|
const user2 = new Parse.User();
|
|
await expectAsync(
|
|
user2.save({ authData: { baseAdapter: { id: 'another' } } })
|
|
).toBeRejectedWithError('this auth is already used');
|
|
});
|
|
|
|
it('should ensure no duplicate auth data id during authData validation in case of more than one result', async () => {
|
|
await reconfigureServer({
|
|
auth: { baseAdapter },
|
|
cloud: () => {
|
|
Parse.Cloud.beforeSave('_User', async request => {
|
|
request.object.set('authData', { baseAdapter: { id: 'test' } });
|
|
});
|
|
},
|
|
});
|
|
|
|
spyOn(Auth, 'findUsersWithAuthData').and.resolveTo([true, true]);
|
|
|
|
const user = new Parse.User();
|
|
await expectAsync(
|
|
user.save({ authData: { baseAdapter: { id: 'another' } } })
|
|
).toBeRejectedWithError('this auth is already used');
|
|
});
|
|
|
|
it('should pass authData, options, request to validateAuthData', async () => {
|
|
spyOn(baseAdapter, 'validateAuthData').and.resolveTo({});
|
|
await reconfigureServer({ auth: { baseAdapter } });
|
|
const user = new Parse.User();
|
|
const payload = { someData: true };
|
|
|
|
await user.save({
|
|
username: 'test',
|
|
password: 'password',
|
|
authData: { baseAdapter: payload },
|
|
});
|
|
|
|
expect(user.getSessionToken()).toBeDefined();
|
|
const firstCall = baseAdapter.validateAuthData.calls.argsFor(0);
|
|
expect(firstCall[0]).toEqual(payload);
|
|
expect(firstCall[1]).toEqual(baseAdapter);
|
|
expect(firstCall[2].object).toBeDefined();
|
|
expect(firstCall[2].original).toBeUndefined();
|
|
expect(firstCall[2].user).toBeUndefined();
|
|
expect(firstCall[2].isChallenge).toBeUndefined();
|
|
expect(firstCall.length).toEqual(3);
|
|
|
|
await request({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/login',
|
|
body: JSON.stringify({
|
|
username: 'test',
|
|
password: 'password',
|
|
authData: { baseAdapter: payload },
|
|
}),
|
|
});
|
|
const secondCall = baseAdapter.validateAuthData.calls.argsFor(1);
|
|
expect(secondCall[0]).toEqual(payload);
|
|
expect(secondCall[1]).toEqual(baseAdapter);
|
|
expect(secondCall[2].original).toBeDefined();
|
|
expect(secondCall[2].original instanceof Parse.User).toBeTruthy();
|
|
expect(secondCall[2].original.id).toEqual(user.id);
|
|
expect(secondCall[2].object).toBeDefined();
|
|
expect(secondCall[2].object instanceof Parse.User).toBeTruthy();
|
|
expect(secondCall[2].object.id).toEqual(user.id);
|
|
expect(secondCall[2].isChallenge).toBeUndefined();
|
|
expect(secondCall[2].user).toBeUndefined();
|
|
expect(secondCall.length).toEqual(3);
|
|
});
|
|
|
|
it('should trigger correctly validateSetUp', async () => {
|
|
spyOn(modernAdapter, 'validateSetUp').and.resolveTo({});
|
|
spyOn(modernAdapter, 'validateUpdate').and.resolveTo({});
|
|
spyOn(modernAdapter, 'validateLogin').and.resolveTo({});
|
|
spyOn(modernAdapter2, 'validateSetUp').and.resolveTo({});
|
|
spyOn(modernAdapter2, 'validateUpdate').and.resolveTo({});
|
|
spyOn(modernAdapter2, 'validateLogin').and.resolveTo({});
|
|
|
|
await reconfigureServer({ auth: { modernAdapter, modernAdapter2 } });
|
|
const user = new Parse.User();
|
|
|
|
await user.save({ authData: { modernAdapter: { id: 'modernAdapter' } } });
|
|
|
|
expect(modernAdapter.validateUpdate).toHaveBeenCalledTimes(0);
|
|
expect(modernAdapter.validateLogin).toHaveBeenCalledTimes(0);
|
|
expect(modernAdapter.validateSetUp).toHaveBeenCalledTimes(1);
|
|
const call = modernAdapter.validateSetUp.calls.argsFor(0);
|
|
expect(call[0]).toEqual({ id: 'modernAdapter' });
|
|
expect(call[1]).toEqual(modernAdapter);
|
|
expect(call[2].isChallenge).toBeUndefined();
|
|
expect(call[2].master).toBeDefined();
|
|
expect(call[2].object instanceof Parse.User).toBeTruthy();
|
|
expect(call[2].user).toBeUndefined();
|
|
expect(call[2].original).toBeUndefined();
|
|
expect(call[2].triggerName).toBe('validateSetUp');
|
|
expect(call.length).toEqual(3);
|
|
expect(user.getSessionToken()).toBeDefined();
|
|
|
|
await user.save(
|
|
{ authData: { modernAdapter2: { id: 'modernAdapter2' } } },
|
|
{ sessionToken: user.getSessionToken() }
|
|
);
|
|
|
|
expect(modernAdapter2.validateUpdate).toHaveBeenCalledTimes(0);
|
|
expect(modernAdapter2.validateLogin).toHaveBeenCalledTimes(0);
|
|
expect(modernAdapter2.validateSetUp).toHaveBeenCalledTimes(1);
|
|
const call2 = modernAdapter2.validateSetUp.calls.argsFor(0);
|
|
expect(call2[0]).toEqual({ id: 'modernAdapter2' });
|
|
expect(call2[1]).toEqual(modernAdapter2);
|
|
expect(call2[2].isChallenge).toBeUndefined();
|
|
expect(call2[2].master).toBeDefined();
|
|
expect(call2[2].object instanceof Parse.User).toBeTruthy();
|
|
expect(call2[2].original instanceof Parse.User).toBeTruthy();
|
|
expect(call2[2].user instanceof Parse.User).toBeTruthy();
|
|
expect(call2[2].original.id).toEqual(call2[2].object.id);
|
|
expect(call2[2].user.id).toEqual(call2[2].object.id);
|
|
expect(call2[2].object.id).toEqual(user.id);
|
|
expect(call2[2].triggerName).toBe('validateSetUp');
|
|
expect(call2.length).toEqual(3);
|
|
|
|
const user2 = new Parse.User();
|
|
user2.id = user.id;
|
|
await user2.fetch({ useMasterKey: true });
|
|
expect(user2.get('authData')).toEqual({
|
|
modernAdapter: { id: 'modernAdapter' },
|
|
modernAdapter2: { id: 'modernAdapter2' },
|
|
});
|
|
});
|
|
|
|
it('should trigger correctly validateLogin', async () => {
|
|
spyOn(modernAdapter, 'validateSetUp').and.resolveTo({});
|
|
spyOn(modernAdapter, 'validateUpdate').and.resolveTo({});
|
|
spyOn(modernAdapter, 'validateLogin').and.resolveTo({});
|
|
|
|
await reconfigureServer({ auth: { modernAdapter }, allowExpiredAuthDataToken: false });
|
|
const user = new Parse.User();
|
|
|
|
await user.save({ authData: { modernAdapter: { id: 'modernAdapter' } } });
|
|
|
|
expect(modernAdapter.validateSetUp).toHaveBeenCalledTimes(1);
|
|
const user2 = new Parse.User();
|
|
await user2.save({ authData: { modernAdapter: { id: 'modernAdapter' } } });
|
|
|
|
expect(modernAdapter.validateUpdate).toHaveBeenCalledTimes(0);
|
|
expect(modernAdapter.validateSetUp).toHaveBeenCalledTimes(1);
|
|
expect(modernAdapter.validateLogin).toHaveBeenCalledTimes(1);
|
|
const call = modernAdapter.validateLogin.calls.argsFor(0);
|
|
expect(call[0]).toEqual({ id: 'modernAdapter' });
|
|
expect(call[1]).toEqual(modernAdapter);
|
|
expect(call[2].object instanceof Parse.User).toBeTruthy();
|
|
expect(call[2].original instanceof Parse.User).toBeTruthy();
|
|
expect(call[2].isChallenge).toBeUndefined();
|
|
expect(call[2].master).toBeDefined();
|
|
expect(call[2].user).toBeUndefined();
|
|
expect(call[2].original.id).toEqual(user2.id);
|
|
expect(call[2].object.id).toEqual(user2.id);
|
|
expect(call[2].object.id).toEqual(user.id);
|
|
expect(call.length).toEqual(3);
|
|
expect(user2.getSessionToken()).toBeDefined();
|
|
});
|
|
|
|
it('should trigger correctly validateUpdate', async () => {
|
|
spyOn(modernAdapter, 'validateSetUp').and.resolveTo({});
|
|
spyOn(modernAdapter, 'validateUpdate').and.resolveTo({});
|
|
spyOn(modernAdapter, 'validateLogin').and.resolveTo({});
|
|
|
|
await reconfigureServer({ auth: { modernAdapter } });
|
|
const user = new Parse.User();
|
|
|
|
await user.save({ authData: { modernAdapter: { id: 'modernAdapter' } } });
|
|
expect(modernAdapter.validateSetUp).toHaveBeenCalledTimes(1);
|
|
|
|
// Save same data
|
|
await user.save(
|
|
{ authData: { modernAdapter: { id: 'modernAdapter' } } },
|
|
{ sessionToken: user.getSessionToken() }
|
|
);
|
|
|
|
// Save same data with master key
|
|
await user.save(
|
|
{ authData: { modernAdapter: { id: 'modernAdapter' } } },
|
|
{ useMasterKey: true }
|
|
);
|
|
|
|
expect(modernAdapter.validateUpdate).toHaveBeenCalledTimes(0);
|
|
expect(modernAdapter.validateSetUp).toHaveBeenCalledTimes(1);
|
|
expect(modernAdapter.validateLogin).toHaveBeenCalledTimes(0);
|
|
|
|
// Change authData
|
|
await user.save(
|
|
{ authData: { modernAdapter: { id: 'modernAdapter2' } } },
|
|
{ sessionToken: user.getSessionToken() }
|
|
);
|
|
|
|
expect(modernAdapter.validateUpdate).toHaveBeenCalledTimes(1);
|
|
expect(modernAdapter.validateSetUp).toHaveBeenCalledTimes(1);
|
|
expect(modernAdapter.validateLogin).toHaveBeenCalledTimes(0);
|
|
const call = modernAdapter.validateUpdate.calls.argsFor(0);
|
|
expect(call[0]).toEqual({ id: 'modernAdapter2' });
|
|
expect(call[1]).toEqual(modernAdapter);
|
|
expect(call[2].isChallenge).toBeUndefined();
|
|
expect(call[2].master).toBeDefined();
|
|
expect(call[2].object instanceof Parse.User).toBeTruthy();
|
|
expect(call[2].user instanceof Parse.User).toBeTruthy();
|
|
expect(call[2].original instanceof Parse.User).toBeTruthy();
|
|
expect(call[2].object.id).toEqual(user.id);
|
|
expect(call[2].original.id).toEqual(user.id);
|
|
expect(call[2].user.id).toEqual(user.id);
|
|
expect(call.length).toEqual(3);
|
|
expect(user.getSessionToken()).toBeDefined();
|
|
});
|
|
|
|
it('should strip out authData if required', async () => {
|
|
const spy = spyOn(modernAdapter3, 'validateOptions').and.callThrough();
|
|
const afterSpy = spyOn(modernAdapter3, 'afterFind').and.callThrough();
|
|
await reconfigureServer({ auth: { modernAdapter3 } });
|
|
const user = new Parse.User();
|
|
await user.save({ authData: { modernAdapter3: { id: 'modernAdapter3Data' } } });
|
|
await user.fetch({ sessionToken: user.getSessionToken() });
|
|
const authData = user.get('authData').modernAdapter3;
|
|
expect(authData).toEqual({ foo: 'bar' });
|
|
for (const call of afterSpy.calls.all()) {
|
|
const args = call.args[0];
|
|
if (args.user) {
|
|
user._objCount = args.user._objCount;
|
|
break;
|
|
}
|
|
}
|
|
expect(afterSpy).toHaveBeenCalledWith(
|
|
{ ip: '127.0.0.1', user, master: false },
|
|
{ id: 'modernAdapter3Data' },
|
|
undefined
|
|
);
|
|
expect(spy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should throw if policy does not match one of default/solo/additional', async () => {
|
|
const adapterWithBadPolicy = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateAuthData: () => Promise.resolve(),
|
|
policy: 'bad',
|
|
};
|
|
await reconfigureServer({ auth: { adapterWithBadPolicy } });
|
|
const user = new Parse.User();
|
|
await expectAsync(
|
|
user.save({ authData: { adapterWithBadPolicy: { id: 'adapterWithBadPolicy' } } })
|
|
).toBeRejectedWithError(
|
|
'AuthAdapter policy is not configured correctly. The value must be either "solo", "additional", "default" or undefined (will be handled as "default")'
|
|
);
|
|
});
|
|
|
|
it('should throw if no triggers found', async () => {
|
|
await reconfigureServer({ auth: { wrongAdapter } });
|
|
const user = new Parse.User();
|
|
await expectAsync(
|
|
user.save({ authData: { wrongAdapter: { id: 'wrongAdapter' } } })
|
|
).toBeRejectedWithError(
|
|
'Adapter is not configured. Implement either validateAuthData or all of the following: validateSetUp, validateLogin and validateUpdate'
|
|
);
|
|
});
|
|
|
|
it('should not update authData if provider return doNotSave', async () => {
|
|
spyOn(doNotSaveAdapter, 'validateAuthData').and.resolveTo({ doNotSave: true });
|
|
await reconfigureServer({
|
|
auth: { doNotSaveAdapter, baseAdapter },
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
|
|
await user.save({
|
|
authData: { baseAdapter: { id: 'baseAdapter' }, doNotSaveAdapter: { token: true } },
|
|
});
|
|
|
|
await user.fetch({ useMasterKey: true });
|
|
|
|
expect(user.get('authData')).toEqual({ baseAdapter: { id: 'baseAdapter' } });
|
|
});
|
|
|
|
it('should loginWith user with auth Adapter with do not save option, mutated authData and no additional auth adapter', async () => {
|
|
const spy = spyOn(doNotSaveAdapter, 'validateAuthData').and.resolveTo({ doNotSave: false });
|
|
await reconfigureServer({
|
|
auth: { doNotSaveAdapter, baseAdapter },
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
|
|
await user.save({
|
|
authData: { doNotSaveAdapter: { id: 'doNotSaveAdapter' } },
|
|
});
|
|
|
|
await user.fetch({ useMasterKey: true });
|
|
|
|
expect(user.get('authData')).toEqual({ doNotSaveAdapter: { id: 'doNotSaveAdapter' } });
|
|
|
|
spy.and.resolveTo({ doNotSave: true });
|
|
|
|
const user2 = await Parse.User.logInWith('doNotSaveAdapter', {
|
|
authData: { id: 'doNotSaveAdapter', example: 'example' },
|
|
});
|
|
expect(user2.getSessionToken()).toBeDefined();
|
|
expect(user2.id).toEqual(user.id);
|
|
});
|
|
|
|
it('should perform authData validation only when its required', async () => {
|
|
spyOn(baseAdapter2, 'validateAuthData').and.resolveTo({});
|
|
spyOn(baseAdapter2, 'validateAppId').and.resolveTo({});
|
|
spyOn(baseAdapter, 'validateAuthData').and.resolveTo({});
|
|
await reconfigureServer({
|
|
auth: { baseAdapter2, baseAdapter },
|
|
allowExpiredAuthDataToken: false,
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
|
|
await user.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { token: true },
|
|
},
|
|
});
|
|
|
|
expect(baseAdapter2.validateAuthData).toHaveBeenCalledTimes(1);
|
|
expect(baseAdapter2.validateAppId).toHaveBeenCalledTimes(1);
|
|
|
|
const user2 = new Parse.User();
|
|
await user2.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
},
|
|
});
|
|
|
|
expect(baseAdapter2.validateAuthData).toHaveBeenCalledTimes(1);
|
|
|
|
const user3 = new Parse.User();
|
|
await user3.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { token: true },
|
|
},
|
|
});
|
|
|
|
expect(baseAdapter2.validateAuthData).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('should not perform authData validation twice when data mutated', async () => {
|
|
spyOn(baseAdapter, 'validateAuthData').and.resolveTo({});
|
|
await reconfigureServer({
|
|
auth: { baseAdapter },
|
|
allowExpiredAuthDataToken: false,
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
|
|
await user.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter', token: "sometoken1" },
|
|
},
|
|
});
|
|
|
|
expect(baseAdapter.validateAuthData).toHaveBeenCalledTimes(1);
|
|
|
|
const user2 = new Parse.User();
|
|
await user2.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter', token: "sometoken2" },
|
|
},
|
|
});
|
|
|
|
expect(baseAdapter.validateAuthData).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('should require additional provider if configured', async () => {
|
|
await reconfigureServer({
|
|
auth: { baseAdapter, additionalAdapter },
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
|
|
await user.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
additionalAdapter: { token: true },
|
|
},
|
|
});
|
|
|
|
const user2 = new Parse.User();
|
|
await expectAsync(
|
|
user2.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
},
|
|
})
|
|
).toBeRejectedWithError('Missing additional authData additionalAdapter');
|
|
expect(user2.getSessionToken()).toBeUndefined();
|
|
|
|
await user2.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
additionalAdapter: { token: true },
|
|
},
|
|
});
|
|
|
|
expect(user2.getSessionToken()).toBeDefined();
|
|
});
|
|
|
|
it('should skip additional provider if used provider is solo', async () => {
|
|
await reconfigureServer({
|
|
auth: { soloAdapter, additionalAdapter },
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
|
|
await user.save({
|
|
authData: {
|
|
soloAdapter: { id: 'soloAdapter' },
|
|
additionalAdapter: { token: true },
|
|
},
|
|
});
|
|
|
|
const user2 = new Parse.User();
|
|
await user2.save({
|
|
authData: {
|
|
soloAdapter: { id: 'soloAdapter' },
|
|
},
|
|
});
|
|
expect(user2.getSessionToken()).toBeDefined();
|
|
});
|
|
|
|
it('should return authData response and save some info on non username login', async () => {
|
|
spyOn(baseAdapter, 'validateAuthData').and.resolveTo({
|
|
response: { someData: true },
|
|
});
|
|
spyOn(baseAdapter2, 'validateAuthData').and.resolveTo({
|
|
response: { someData2: true },
|
|
save: { otherData: true },
|
|
});
|
|
await reconfigureServer({
|
|
auth: { baseAdapter, baseAdapter2 },
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
|
|
await user.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { test: true },
|
|
},
|
|
});
|
|
|
|
expect(user.get('authDataResponse')).toEqual({
|
|
baseAdapter: { someData: true },
|
|
baseAdapter2: { someData2: true },
|
|
});
|
|
|
|
const user2 = new Parse.User();
|
|
user2.id = user.id;
|
|
await user2.save(
|
|
{
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { test: true },
|
|
},
|
|
},
|
|
{ sessionToken: user.getSessionToken() }
|
|
);
|
|
|
|
expect(user2.get('authDataResponse')).toEqual({ baseAdapter2: { someData2: true } });
|
|
|
|
const user3 = new Parse.User();
|
|
await user3.save({
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { test: true },
|
|
},
|
|
});
|
|
|
|
// On logIn all authData are revalidated
|
|
expect(user3.get('authDataResponse')).toEqual({
|
|
baseAdapter: { someData: true },
|
|
baseAdapter2: { someData2: true },
|
|
});
|
|
|
|
const userViaMasterKey = new Parse.User();
|
|
userViaMasterKey.id = user2.id;
|
|
await userViaMasterKey.fetch({ useMasterKey: true });
|
|
expect(userViaMasterKey.get('authData')).toEqual({
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { otherData: true },
|
|
});
|
|
});
|
|
|
|
it('should return authData response and save some info on username login', async () => {
|
|
spyOn(baseAdapter, 'validateAuthData').and.resolveTo({
|
|
response: { someData: true },
|
|
});
|
|
spyOn(baseAdapter2, 'validateAuthData').and.resolveTo({
|
|
response: { someData2: true },
|
|
save: { otherData: true },
|
|
});
|
|
await reconfigureServer({
|
|
auth: { baseAdapter, baseAdapter2 },
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
|
|
await user.save({
|
|
username: 'username',
|
|
password: 'password',
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { test: true },
|
|
},
|
|
});
|
|
|
|
expect(user.get('authDataResponse')).toEqual({
|
|
baseAdapter: { someData: true },
|
|
baseAdapter2: { someData2: true },
|
|
});
|
|
|
|
const res = await request({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/login',
|
|
body: JSON.stringify({
|
|
username: 'username',
|
|
password: 'password',
|
|
authData: {
|
|
baseAdapter2: { test: true },
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
},
|
|
}),
|
|
});
|
|
const result = res.data;
|
|
expect(result.authDataResponse).toEqual({
|
|
baseAdapter2: { someData2: true },
|
|
baseAdapter: { someData: true },
|
|
});
|
|
|
|
await user.fetch({ useMasterKey: true });
|
|
expect(user.get('authData')).toEqual({
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { otherData: true },
|
|
});
|
|
});
|
|
|
|
describe('should allow update of authData', () => {
|
|
beforeEach(async () => {
|
|
spyOn(baseAdapter, 'validateAuthData').and.resolveTo({
|
|
response: { someData: true },
|
|
});
|
|
spyOn(baseAdapter2, 'validateAuthData').and.resolveTo({
|
|
response: { someData2: true },
|
|
save: { otherData: true },
|
|
});
|
|
await reconfigureServer({
|
|
auth: { baseAdapter, baseAdapter2 },
|
|
});
|
|
});
|
|
|
|
it('should not re validate the baseAdapter when user is already logged in and authData not changed', async () => {
|
|
const user = new Parse.User();
|
|
|
|
await user.save({
|
|
username: 'username',
|
|
password: 'password',
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { test: true },
|
|
},
|
|
});
|
|
expect(baseAdapter.validateAuthData).toHaveBeenCalledTimes(1);
|
|
|
|
expect(user.id).toBeDefined();
|
|
expect(user.getSessionToken()).toBeDefined();
|
|
await user.save(
|
|
{
|
|
authData: {
|
|
baseAdapter2: { test: true },
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
},
|
|
},
|
|
{ sessionToken: user.getSessionToken() }
|
|
);
|
|
|
|
expect(baseAdapter.validateAuthData).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should not re-validate the baseAdapter when master key is used and authData has not changed', async () => {
|
|
const user = new Parse.User();
|
|
await user.save({
|
|
username: 'username',
|
|
password: 'password',
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { test: true },
|
|
},
|
|
});
|
|
await user.save(
|
|
{
|
|
authData: {
|
|
baseAdapter2: { test: true },
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
},
|
|
},
|
|
{ useMasterKey: true }
|
|
);
|
|
|
|
expect(baseAdapter.validateAuthData).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should allow user to change authData', async () => {
|
|
const user = new Parse.User();
|
|
await user.save({
|
|
username: 'username',
|
|
password: 'password',
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { test: true },
|
|
},
|
|
});
|
|
await user.save(
|
|
{
|
|
authData: {
|
|
baseAdapter2: { test: true },
|
|
baseAdapter: { id: 'baseAdapter2' },
|
|
},
|
|
},
|
|
{ sessionToken: user.getSessionToken() }
|
|
);
|
|
|
|
expect(baseAdapter.validateAuthData).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('should allow master key to change authData', async () => {
|
|
const user = new Parse.User();
|
|
await user.save({
|
|
username: 'username',
|
|
password: 'password',
|
|
authData: {
|
|
baseAdapter: { id: 'baseAdapter' },
|
|
baseAdapter2: { test: true },
|
|
},
|
|
});
|
|
await user.save(
|
|
{
|
|
authData: {
|
|
baseAdapter2: { test: true },
|
|
baseAdapter: { id: 'baseAdapter3' },
|
|
},
|
|
},
|
|
{ useMasterKey: true }
|
|
);
|
|
|
|
expect(baseAdapter.validateAuthData).toHaveBeenCalledTimes(2);
|
|
|
|
await user.fetch({ useMasterKey: true });
|
|
expect(user.get('authData')).toEqual({
|
|
baseAdapter: { id: 'baseAdapter3' },
|
|
baseAdapter2: { otherData: true },
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should pass user to auth adapter on update by matching session', async () => {
|
|
spyOn(baseAdapter2, 'validateAuthData').and.resolveTo({});
|
|
await reconfigureServer({ auth: { baseAdapter2 } });
|
|
|
|
const user = new Parse.User();
|
|
|
|
const payload = { someData: true };
|
|
|
|
await user.save({
|
|
username: 'test',
|
|
password: 'password',
|
|
});
|
|
|
|
expect(user.getSessionToken()).toBeDefined();
|
|
|
|
await user.save(
|
|
{ authData: { baseAdapter2: payload } },
|
|
{ sessionToken: user.getSessionToken() }
|
|
);
|
|
|
|
const firstCall = baseAdapter2.validateAuthData.calls.argsFor(0);
|
|
expect(firstCall[0]).toEqual(payload);
|
|
expect(firstCall[1]).toEqual(baseAdapter2);
|
|
expect(firstCall[2].isChallenge).toBeUndefined();
|
|
expect(firstCall[2].master).toBeDefined();
|
|
expect(firstCall[2].object instanceof Parse.User).toBeTruthy();
|
|
expect(firstCall[2].user instanceof Parse.User).toBeTruthy();
|
|
expect(firstCall[2].original instanceof Parse.User).toBeTruthy();
|
|
expect(firstCall[2].object.id).toEqual(user.id);
|
|
expect(firstCall[2].original.id).toEqual(user.id);
|
|
expect(firstCall[2].user.id).toEqual(user.id);
|
|
expect(firstCall.length).toEqual(3);
|
|
|
|
await user.save({ authData: { baseAdapter2: payload } }, { useMasterKey: true });
|
|
|
|
const secondCall = baseAdapter2.validateAuthData.calls.argsFor(1);
|
|
expect(secondCall[0]).toEqual(payload);
|
|
expect(secondCall[1]).toEqual(baseAdapter2);
|
|
expect(secondCall[2].isChallenge).toBeUndefined();
|
|
expect(secondCall[2].master).toEqual(true);
|
|
expect(secondCall[2].user).toBeUndefined();
|
|
expect(secondCall[2].object instanceof Parse.User).toBeTruthy();
|
|
expect(secondCall[2].original instanceof Parse.User).toBeTruthy();
|
|
expect(secondCall[2].object.id).toEqual(user.id);
|
|
expect(secondCall[2].original.id).toEqual(user.id);
|
|
expect(secondCall.length).toEqual(3);
|
|
});
|
|
|
|
it('should return custom errors', async () => {
|
|
const throwInChallengeAdapter = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateAuthData: () => Promise.resolve(),
|
|
challenge: () => Promise.reject('Invalid challenge data: yolo'),
|
|
options: {
|
|
anOption: true,
|
|
},
|
|
};
|
|
const throwInSetup = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateSetUp: () => Promise.reject('You cannot signup with that setup data.'),
|
|
validateUpdate: () => Promise.resolve(),
|
|
validateLogin: () => Promise.resolve(),
|
|
};
|
|
|
|
const throwInUpdate = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateSetUp: () => Promise.resolve(),
|
|
validateUpdate: () => Promise.reject('You cannot update with that update data.'),
|
|
validateLogin: () => Promise.resolve(),
|
|
};
|
|
|
|
const throwInLogin = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateSetUp: () => Promise.resolve(),
|
|
validateUpdate: () => Promise.resolve(),
|
|
validateLogin: () => Promise.reject('You cannot login with that login data.'),
|
|
};
|
|
await reconfigureServer({
|
|
auth: { challengeAdapter: throwInChallengeAdapter },
|
|
});
|
|
let logger = require('../lib/logger').logger;
|
|
spyOn(logger, 'error').and.callFake(() => {});
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
}),
|
|
})
|
|
).toBeRejectedWithError('Invalid challenge data: yolo');
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
`Failed running auth step challenge for challengeAdapter for user undefined with Error: {"message":"Invalid challenge data: yolo","code":${Parse.Error.SCRIPT_FAILED}}`,
|
|
{
|
|
authenticationStep: 'challenge',
|
|
error: new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid challenge data: yolo'),
|
|
user: undefined,
|
|
provider: 'challengeAdapter',
|
|
}
|
|
);
|
|
|
|
await reconfigureServer({ auth: { modernAdapter: throwInSetup } });
|
|
logger = require('../lib/logger').logger;
|
|
spyOn(logger, 'error').and.callFake(() => {});
|
|
let user = new Parse.User();
|
|
await expectAsync(
|
|
user.save({ authData: { modernAdapter: { id: 'modernAdapter' } } })
|
|
).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.SCRIPT_FAILED, 'You cannot signup with that setup data.')
|
|
);
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
`Failed running auth step validateSetUp for modernAdapter for user undefined with Error: {"message":"You cannot signup with that setup data.","code":${Parse.Error.SCRIPT_FAILED}}`,
|
|
{
|
|
authenticationStep: 'validateSetUp',
|
|
error: new Parse.Error(
|
|
Parse.Error.SCRIPT_FAILED,
|
|
'You cannot signup with that setup data.'
|
|
),
|
|
user: undefined,
|
|
provider: 'modernAdapter',
|
|
}
|
|
);
|
|
|
|
await reconfigureServer({ auth: { modernAdapter: throwInUpdate } });
|
|
logger = require('../lib/logger').logger;
|
|
spyOn(logger, 'error').and.callFake(() => {});
|
|
user = new Parse.User();
|
|
await user.save({ authData: { modernAdapter: { id: 'updateAdapter' } } });
|
|
await expectAsync(
|
|
user.save(
|
|
{ authData: { modernAdapter: { id: 'updateAdapter2' } } },
|
|
{ sessionToken: user.getSessionToken() }
|
|
)
|
|
).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.SCRIPT_FAILED, 'You cannot update with that update data.')
|
|
);
|
|
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
`Failed running auth step validateUpdate for modernAdapter for user ${user.id} with Error: {"message":"You cannot update with that update data.","code":${Parse.Error.SCRIPT_FAILED}}`,
|
|
{
|
|
authenticationStep: 'validateUpdate',
|
|
error: new Parse.Error(
|
|
Parse.Error.SCRIPT_FAILED,
|
|
'You cannot update with that update data.'
|
|
),
|
|
user: user.id,
|
|
provider: 'modernAdapter',
|
|
}
|
|
);
|
|
|
|
await reconfigureServer({
|
|
auth: { modernAdapter: throwInLogin },
|
|
allowExpiredAuthDataToken: false,
|
|
});
|
|
logger = require('../lib/logger').logger;
|
|
spyOn(logger, 'error').and.callFake(() => {});
|
|
user = new Parse.User();
|
|
await user.save({ authData: { modernAdapter: { id: 'modernAdapter' } } });
|
|
const user2 = new Parse.User();
|
|
await expectAsync(
|
|
user2.save({ authData: { modernAdapter: { id: 'modernAdapter' } } })
|
|
).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.SCRIPT_FAILED, 'You cannot login with that login data.')
|
|
);
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
`Failed running auth step validateLogin for modernAdapter for user ${user.id} with Error: {"message":"You cannot login with that login data.","code":${Parse.Error.SCRIPT_FAILED}}`,
|
|
{
|
|
authenticationStep: 'validateLogin',
|
|
error: new Parse.Error(Parse.Error.SCRIPT_FAILED, 'You cannot login with that login data.'),
|
|
user: user.id,
|
|
provider: 'modernAdapter',
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should return challenge with no logged user', async () => {
|
|
spyOn(challengeAdapter, 'challenge').and.resolveTo({ token: 'test' });
|
|
|
|
await reconfigureServer({
|
|
auth: { challengeAdapter },
|
|
});
|
|
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: {},
|
|
})
|
|
).toBeRejectedWithError('Nothing to challenge.');
|
|
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: { challengeData: true },
|
|
})
|
|
).toBeRejectedWithError('challengeData should be an object.');
|
|
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: { challengeData: { data: true }, authData: true },
|
|
})
|
|
).toBeRejectedWithError('authData should be an object.');
|
|
|
|
const res = await request({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
}),
|
|
});
|
|
|
|
expect(res.data).toEqual({
|
|
challengeData: {
|
|
challengeAdapter: {
|
|
token: 'test',
|
|
},
|
|
},
|
|
});
|
|
const challengeCall = challengeAdapter.challenge.calls.argsFor(0);
|
|
expect(challengeAdapter.challenge).toHaveBeenCalledTimes(1);
|
|
expect(challengeCall[0]).toEqual({ someData: true });
|
|
expect(challengeCall[1]).toBeUndefined();
|
|
expect(challengeCall[2]).toEqual(challengeAdapter);
|
|
expect(challengeCall[3].master).toBeDefined();
|
|
expect(challengeCall[3].headers).toBeDefined();
|
|
expect(challengeCall[3].object).toBeUndefined();
|
|
expect(challengeCall[3].original).toBeUndefined();
|
|
expect(challengeCall[3].user).toBeUndefined();
|
|
expect(challengeCall[3].isChallenge).toBeTruthy();
|
|
expect(challengeCall.length).toEqual(4);
|
|
});
|
|
|
|
it('should return empty challenge data response if challenged provider does not exists', async () => {
|
|
spyOn(challengeAdapter, 'challenge').and.resolveTo({ token: 'test' });
|
|
|
|
await reconfigureServer({
|
|
auth: { challengeAdapter },
|
|
});
|
|
|
|
const res = await request({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
challengeData: {
|
|
nonExistingProvider: { someData: true },
|
|
},
|
|
}),
|
|
});
|
|
|
|
expect(res.data).toEqual({ challengeData: {} });
|
|
});
|
|
it('should return challenge with username created user', async () => {
|
|
spyOn(challengeAdapter, 'challenge').and.resolveTo({ token: 'test' });
|
|
|
|
await reconfigureServer({
|
|
auth: { challengeAdapter },
|
|
});
|
|
|
|
const user = new Parse.User();
|
|
await user.save({ username: 'username', password: 'password' });
|
|
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
username: 'username',
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
}),
|
|
})
|
|
).toBeRejectedWithError('You provided username or email, you need to also provide password.');
|
|
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
username: 'username',
|
|
password: 'password',
|
|
authData: { data: true },
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
}),
|
|
})
|
|
).toBeRejectedWithError(
|
|
'You cannot provide username/email and authData, only use one identification method.'
|
|
);
|
|
|
|
const res = await request({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
username: 'username',
|
|
password: 'password',
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
}),
|
|
});
|
|
|
|
expect(res.data).toEqual({
|
|
challengeData: {
|
|
challengeAdapter: {
|
|
token: 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
const challengeCall = challengeAdapter.challenge.calls.argsFor(0);
|
|
expect(challengeAdapter.challenge).toHaveBeenCalledTimes(1);
|
|
expect(challengeCall[0]).toEqual({ someData: true });
|
|
expect(challengeCall[1]).toEqual(undefined);
|
|
expect(challengeCall[2]).toEqual(challengeAdapter);
|
|
expect(challengeCall[3].master).toBeDefined();
|
|
expect(challengeCall[3].isChallenge).toBeTruthy();
|
|
expect(challengeCall[3].user).toBeUndefined();
|
|
expect(challengeCall[3].object instanceof Parse.User).toBeTruthy();
|
|
expect(challengeCall[3].original instanceof Parse.User).toBeTruthy();
|
|
expect(challengeCall[3].object.id).toEqual(user.id);
|
|
expect(challengeCall[3].original.id).toEqual(user.id);
|
|
expect(challengeCall.length).toEqual(4);
|
|
});
|
|
|
|
it('should return challenge with authData created user', async () => {
|
|
spyOn(challengeAdapter, 'challenge').and.resolveTo({ token: 'test' });
|
|
spyOn(challengeAdapter, 'validateAuthData').and.callThrough();
|
|
|
|
await reconfigureServer({
|
|
auth: { challengeAdapter, soloAdapter },
|
|
});
|
|
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
authData: {
|
|
challengeAdapter: { id: 'challengeAdapter' },
|
|
},
|
|
}),
|
|
})
|
|
).toBeRejectedWithError('User not found.');
|
|
|
|
const user = new Parse.User();
|
|
await user.save({ authData: { challengeAdapter: { id: 'challengeAdapter' } } });
|
|
|
|
const user2 = new Parse.User();
|
|
await user2.save({ authData: { soloAdapter: { id: 'soloAdapter' } } });
|
|
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
authData: {
|
|
challengeAdapter: { id: 'challengeAdapter' },
|
|
soloAdapter: { id: 'soloAdapter' },
|
|
},
|
|
}),
|
|
})
|
|
).toBeRejectedWithError('You cannot provide more than one authData provider with an id.');
|
|
|
|
const res = await request({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
authData: {
|
|
challengeAdapter: { id: 'challengeAdapter' },
|
|
},
|
|
}),
|
|
});
|
|
|
|
expect(res.data).toEqual({
|
|
challengeData: {
|
|
challengeAdapter: {
|
|
token: 'test',
|
|
},
|
|
},
|
|
});
|
|
|
|
const validateCall = challengeAdapter.validateAuthData.calls.argsFor(1);
|
|
expect(validateCall[2].isChallenge).toBeTruthy();
|
|
|
|
const challengeCall = challengeAdapter.challenge.calls.argsFor(0);
|
|
expect(challengeAdapter.challenge).toHaveBeenCalledTimes(1);
|
|
expect(challengeCall[0]).toEqual({ someData: true });
|
|
expect(challengeCall[1]).toEqual({ id: 'challengeAdapter' });
|
|
expect(challengeCall[2]).toEqual(challengeAdapter);
|
|
expect(challengeCall[3].master).toBeDefined();
|
|
expect(challengeCall[3].isChallenge).toBeTruthy();
|
|
expect(challengeCall[3].object instanceof Parse.User).toBeTruthy();
|
|
expect(challengeCall[3].original instanceof Parse.User).toBeTruthy();
|
|
expect(challengeCall[3].object.id).toEqual(user.id);
|
|
expect(challengeCall[3].original.id).toEqual(user.id);
|
|
expect(challengeCall.length).toEqual(4);
|
|
});
|
|
|
|
it('should validate provided authData and prevent guess id attack', async () => {
|
|
spyOn(challengeAdapter, 'challenge').and.resolveTo({ token: 'test' });
|
|
|
|
await reconfigureServer({
|
|
auth: { challengeAdapter, soloAdapter },
|
|
});
|
|
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
authData: {
|
|
challengeAdapter: { id: 'challengeAdapter' },
|
|
},
|
|
}),
|
|
})
|
|
).toBeRejectedWithError('User not found.');
|
|
|
|
const user = new Parse.User();
|
|
await user.save({ authData: { challengeAdapter: { id: 'challengeAdapter' } } });
|
|
|
|
spyOn(challengeAdapter, 'validateAuthData').and.rejectWith({});
|
|
|
|
await expectAsync(
|
|
requestWithExpectedError({
|
|
headers: headers,
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/challenge',
|
|
body: JSON.stringify({
|
|
challengeData: {
|
|
challengeAdapter: { someData: true },
|
|
},
|
|
authData: {
|
|
challengeAdapter: { id: 'challengeAdapter' },
|
|
},
|
|
}),
|
|
})
|
|
).toBeRejectedWithError('User not found.');
|
|
|
|
const validateCall = challengeAdapter.validateAuthData.calls.argsFor(0);
|
|
expect(challengeAdapter.validateAuthData).toHaveBeenCalledTimes(1);
|
|
expect(validateCall[0]).toEqual({ id: 'challengeAdapter' });
|
|
expect(validateCall[1]).toEqual(challengeAdapter);
|
|
expect(validateCall[2].isChallenge).toBeTruthy();
|
|
expect(validateCall[2].master).toBeDefined();
|
|
expect(validateCall[2].object instanceof Parse.User).toBeTruthy();
|
|
expect(validateCall[2].original instanceof Parse.User).toBeTruthy();
|
|
expect(validateCall[2].object.id).toEqual(user.id);
|
|
expect(validateCall[2].original.id).toEqual(user.id);
|
|
expect(validateCall.length).toEqual(3);
|
|
});
|
|
|
|
it('should work with multiple adapters', async () => {
|
|
const adapterA = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateAuthData: () => Promise.resolve(),
|
|
};
|
|
const adapterB = {
|
|
validateAppId: () => Promise.resolve(),
|
|
validateAuthData: () => Promise.resolve(),
|
|
};
|
|
await reconfigureServer({ auth: { adapterA, adapterB } });
|
|
const user = new Parse.User();
|
|
await user.signUp({
|
|
username: 'test',
|
|
password: 'password',
|
|
});
|
|
await user.save({ authData: { adapterA: { id: 'testA' } } });
|
|
expect(user.get('authData')).toEqual({ adapterA: { id: 'testA' } });
|
|
await user.save({ authData: { adapterA: null, adapterB: { id: 'test' } } });
|
|
await user.fetch({ useMasterKey: true });
|
|
expect(user.get('authData')).toEqual({ adapterB: { id: 'test' } });
|
|
});
|
|
});
|