feat: Add afterFind trigger to authentication adapters (#8444)

This commit is contained in:
Daniel
2023-03-06 11:35:15 +11:00
committed by GitHub
parent 87cab09b6a
commit c793bb88e7
6 changed files with 95 additions and 4 deletions

View File

@@ -59,6 +59,19 @@ describe('Auth Adapter features', () => {
validateLogin: () => 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 = { const wrongAdapter = {
validateAppId: () => Promise.resolve(), validateAppId: () => Promise.resolve(),
}; };
@@ -332,6 +345,17 @@ describe('Auth Adapter features', () => {
expect(user.getSessionToken()).toBeDefined(); expect(user.getSessionToken()).toBeDefined();
}); });
it('should strip out authData if required', async () => {
const spy = spyOn(modernAdapter3, 'validateOptions').and.callThrough();
await reconfigureServer({ auth: { modernAdapter3 }, silent: false });
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' });
expect(spy).toHaveBeenCalled();
});
it('should throw if no triggers found', async () => { it('should throw if no triggers found', async () => {
await reconfigureServer({ auth: { wrongAdapter } }); await reconfigureServer({ auth: { wrongAdapter } });
const user = new Parse.User(); const user = new Parse.User();

View File

@@ -248,7 +248,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
expect(object.date[0] instanceof Date).toBeTrue(); expect(object.date[0] instanceof Date).toBeTrue();
expect(object.bar.date[0] instanceof Date).toBeTrue(); expect(object.bar.date[0] instanceof Date).toBeTrue();
expect(object.foo.test.date[0] instanceof Date).toBeTrue(); expect(object.foo.test.date[0] instanceof Date).toBeTrue();
const obj = await new Parse.Query('MyClass').first({useMasterKey: true}); const obj = await new Parse.Query('MyClass').first({ useMasterKey: true });
expect(obj.get('date')[0] instanceof Date).toBeTrue(); expect(obj.get('date')[0] instanceof Date).toBeTrue();
expect(obj.get('bar').date[0] instanceof Date).toBeTrue(); expect(obj.get('bar').date[0] instanceof Date).toBeTrue();
expect(obj.get('foo').test.date[0] instanceof Date).toBeTrue(); expect(obj.get('foo').test.date[0] instanceof Date).toBeTrue();

View File

@@ -93,6 +93,24 @@ export class AuthAdapter {
challenge(challengeData, authData, options, request) { challenge(challengeData, authData, options, request) {
return Promise.resolve({}); return Promise.resolve({});
} }
/**
* Triggered when auth data is fetched
* @param {Object} authData authData
* @param {Object} options additional adapter options
* @returns {Promise<Object>} Any overrides required to authData
*/
afterFind(authData, options) {
return Promise.resolve({});
}
/**
* Triggered when the adapter is first attached to Parse Server
* @param {Object} options Adapter Options
*/
validateOptions(options) {
/* */
}
} }
export default AuthAdapter; export default AuthAdapter;

View File

@@ -154,7 +154,8 @@ function loadAuthAdapter(provider, authOptions) {
return; return;
} }
const adapter = defaultAdapter instanceof AuthAdapter ? defaultAdapter : Object.assign({}, defaultAdapter); const adapter =
defaultAdapter instanceof AuthAdapter ? defaultAdapter : Object.assign({}, defaultAdapter);
const keys = [ const keys = [
'validateAuthData', 'validateAuthData',
'validateAppId', 'validateAppId',
@@ -162,12 +163,18 @@ function loadAuthAdapter(provider, authOptions) {
'validateLogin', 'validateLogin',
'validateUpdate', 'validateUpdate',
'challenge', 'challenge',
'policy' 'validateOptions',
'policy',
'afterFind',
]; ];
const defaultAuthAdapter = new AuthAdapter(); const defaultAuthAdapter = new AuthAdapter();
keys.forEach(key => { keys.forEach(key => {
const existing = adapter?.[key]; const existing = adapter?.[key];
if (existing && typeof existing === 'function' && existing.toString() === defaultAuthAdapter[key].toString()) { if (
existing &&
typeof existing === 'function' &&
existing.toString() === defaultAuthAdapter[key].toString()
) {
adapter[key] = null; adapter[key] = null;
} }
}); });
@@ -184,6 +191,9 @@ function loadAuthAdapter(provider, authOptions) {
}); });
} }
} }
if (adapter.validateOptions) {
adapter.validateOptions(providerOptions);
}
return { adapter, appIds, providerOptions }; return { adapter, appIds, providerOptions };
} }
@@ -204,9 +214,35 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) {
return { validator: authDataValidator(provider, adapter, appIds, providerOptions), adapter }; return { validator: authDataValidator(provider, adapter, appIds, providerOptions), adapter };
}; };
const runAfterFind = async authData => {
if (!authData) {
return;
}
const adapters = Object.keys(authData);
await Promise.all(
adapters.map(async provider => {
const authAdapter = getValidatorForProvider(provider);
if (!authAdapter) {
return;
}
const {
adapter: { afterFind },
providerOptions,
} = authAdapter;
if (afterFind && typeof afterFind === 'function') {
const result = afterFind(authData[provider], providerOptions);
if (result) {
authData[provider] = result;
}
}
})
);
};
return Object.freeze({ return Object.freeze({
getValidatorForProvider, getValidatorForProvider,
setEnableAnonymousUsers, setEnableAnonymousUsers,
runAfterFind,
}); });
}; };

View File

@@ -223,6 +223,9 @@ RestQuery.prototype.execute = function (executeOptions) {
.then(() => { .then(() => {
return this.runAfterFindTrigger(); return this.runAfterFindTrigger();
}) })
.then(() => {
return this.handleAuthAdapters();
})
.then(() => { .then(() => {
return this.response; return this.response;
}); });
@@ -842,6 +845,15 @@ RestQuery.prototype.runAfterFindTrigger = function () {
}); });
}; };
RestQuery.prototype.handleAuthAdapters = async function () {
if (this.className !== '_User' || this.findOptions.explain) {
return;
}
await Promise.all(
this.response.results.map(result => this.config.authDataManager.runAfterFind(result.authData))
);
};
// Adds included values to the response. // Adds included values to the response.
// Path is a list of field names. // Path is a list of field names.
// Returns a promise for an augmented response. // Returns a promise for an augmented response.

View File

@@ -292,6 +292,7 @@ export class UsersRouter extends ClassesRouter {
if (authDataResponse) { if (authDataResponse) {
user.authDataResponse = authDataResponse; user.authDataResponse = authDataResponse;
} }
await req.config.authDataManager.runAfterFind(user.authData);
return { response: user }; return { response: user };
} }