Skip to content

feat: Add afterFind trigger to authentication adapters #8444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions spec/AuthenticationAdaptersV2.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ describe('Auth Adapter features', () => {
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(),
};
Expand Down Expand Up @@ -332,6 +345,17 @@ describe('Auth Adapter features', () => {
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 () => {
await reconfigureServer({ auth: { wrongAdapter } });
const user = new Parse.User();
Expand Down
2 changes: 1 addition & 1 deletion spec/MongoStorageAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
expect(object.date[0] instanceof Date).toBeTrue();
expect(object.bar.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('bar').date[0] instanceof Date).toBeTrue();
expect(obj.get('foo').test.date[0] instanceof Date).toBeTrue();
Expand Down
18 changes: 18 additions & 0 deletions src/Adapters/Auth/AuthAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,24 @@ export class AuthAdapter {
challenge(challengeData, authData, options, request) {
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;
42 changes: 39 additions & 3 deletions src/Adapters/Auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,20 +154,27 @@ function loadAuthAdapter(provider, authOptions) {
return;
}

const adapter = defaultAdapter instanceof AuthAdapter ? defaultAdapter : Object.assign({}, defaultAdapter);
const adapter =
defaultAdapter instanceof AuthAdapter ? defaultAdapter : Object.assign({}, defaultAdapter);
const keys = [
'validateAuthData',
'validateAppId',
'validateSetUp',
'validateLogin',
'validateUpdate',
'challenge',
'policy'
'validateOptions',
'policy',
'afterFind',
];
const defaultAuthAdapter = new AuthAdapter();
keys.forEach(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;
}
});
Expand All @@ -184,6 +191,9 @@ function loadAuthAdapter(provider, authOptions) {
});
}
}
if (adapter.validateOptions) {
adapter.validateOptions(providerOptions);
}

return { adapter, appIds, providerOptions };
}
Expand All @@ -204,9 +214,35 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) {
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({
getValidatorForProvider,
setEnableAnonymousUsers,
runAfterFind,
});
};

Expand Down
12 changes: 12 additions & 0 deletions src/RestQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ RestQuery.prototype.execute = function (executeOptions) {
.then(() => {
return this.runAfterFindTrigger();
})
.then(() => {
return this.handleAuthAdapters();
})
.then(() => {
return this.response;
});
Expand Down Expand Up @@ -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.
// Path is a list of field names.
// Returns a promise for an augmented response.
Expand Down
1 change: 1 addition & 0 deletions src/Routers/UsersRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ export class UsersRouter extends ClassesRouter {
if (authDataResponse) {
user.authDataResponse = authDataResponse;
}
await req.config.authDataManager.runAfterFind(user.authData);

return { response: user };
}
Expand Down