Add beforeLogin trigger with support for auth providers (#5445)
* Add beforeLogin trigger with support for auth providers * adjust comment that boxed off beforeLogin to a negative use-case only * add internal error to help future maintainers regarding use of beforeLogin * let beforeLogin accept className or constructor like other hook types * add assertions for beforeLogin trigger className validation
This commit is contained in:
committed by
Arthur Cinader
parent
3e003ee9f4
commit
a1e1cef6d2
@@ -2021,6 +2021,24 @@ describe('afterFind hooks', () => {
|
|||||||
expect(() => {
|
expect(() => {
|
||||||
Parse.Cloud.afterSave('_PushStatus', () => {});
|
Parse.Cloud.afterSave('_PushStatus', () => {});
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
|
expect(() => {
|
||||||
|
Parse.Cloud.beforeLogin(() => {});
|
||||||
|
}).not.toThrow(
|
||||||
|
'Only the _User class is allowed for the beforeLogin trigger'
|
||||||
|
);
|
||||||
|
expect(() => {
|
||||||
|
Parse.Cloud.beforeLogin('_User', () => {});
|
||||||
|
}).not.toThrow(
|
||||||
|
'Only the _User class is allowed for the beforeLogin trigger'
|
||||||
|
);
|
||||||
|
expect(() => {
|
||||||
|
Parse.Cloud.beforeLogin(Parse.User, () => {});
|
||||||
|
}).not.toThrow(
|
||||||
|
'Only the _User class is allowed for the beforeLogin trigger'
|
||||||
|
);
|
||||||
|
expect(() => {
|
||||||
|
Parse.Cloud.beforeLogin('SomeClass', () => {});
|
||||||
|
}).toThrow('Only the _User class is allowed for the beforeLogin trigger');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip afterFind hooks for aggregate', done => {
|
it('should skip afterFind hooks for aggregate', done => {
|
||||||
@@ -2115,3 +2133,88 @@ describe('afterFind hooks', () => {
|
|||||||
expect(calledAfter).toBe(true);
|
expect(calledAfter).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('beforeLogin hook', () => {
|
||||||
|
it('should run beforeLogin with correct credentials', async done => {
|
||||||
|
let hit = 0;
|
||||||
|
Parse.Cloud.beforeLogin(req => {
|
||||||
|
hit++;
|
||||||
|
expect(req.object.get('username')).toEqual('tupac');
|
||||||
|
});
|
||||||
|
|
||||||
|
await Parse.User.signUp('tupac', 'shakur');
|
||||||
|
const user = await Parse.User.logIn('tupac', 'shakur');
|
||||||
|
expect(hit).toBe(1);
|
||||||
|
expect(user).toBeDefined();
|
||||||
|
expect(user.getUsername()).toBe('tupac');
|
||||||
|
expect(user.getSessionToken()).toBeDefined();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to block login if an error is thrown', async done => {
|
||||||
|
let hit = 0;
|
||||||
|
Parse.Cloud.beforeLogin(req => {
|
||||||
|
hit++;
|
||||||
|
if (req.object.get('isBanned')) {
|
||||||
|
throw new Error('banned account');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await Parse.User.signUp('tupac', 'shakur');
|
||||||
|
await user.save({ isBanned: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Parse.User.logIn('tupac', 'shakur');
|
||||||
|
throw new Error('should not have been logged in.');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('banned account');
|
||||||
|
}
|
||||||
|
expect(hit).toBe(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not run beforeLogin with incorrect credentials', async done => {
|
||||||
|
let hit = 0;
|
||||||
|
Parse.Cloud.beforeLogin(req => {
|
||||||
|
hit++;
|
||||||
|
expect(req.object.get('username')).toEqual('tupac');
|
||||||
|
});
|
||||||
|
|
||||||
|
await Parse.User.signUp('tupac', 'shakur');
|
||||||
|
try {
|
||||||
|
await Parse.User.logIn('tony', 'shakur');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
expect(hit).toBe(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not run beforeLogin on sign up', async done => {
|
||||||
|
let hit = 0;
|
||||||
|
Parse.Cloud.beforeLogin(req => {
|
||||||
|
hit++;
|
||||||
|
expect(req.object.get('username')).toEqual('tupac');
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await Parse.User.signUp('tupac', 'shakur');
|
||||||
|
expect(user).toBeDefined();
|
||||||
|
expect(hit).toBe(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have expected data in request', async done => {
|
||||||
|
Parse.Cloud.beforeLogin(req => {
|
||||||
|
expect(req.object).toBeDefined();
|
||||||
|
expect(req.user).toBeUndefined();
|
||||||
|
expect(req.headers).toBeDefined();
|
||||||
|
expect(req.ip).toBeDefined();
|
||||||
|
expect(req.installationId).toBeDefined();
|
||||||
|
expect(req.context).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
await Parse.User.signUp('tupac', 'shakur');
|
||||||
|
await Parse.User.logIn('tupac', 'shakur');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -440,6 +440,22 @@ describe('Parse.User testing', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not call beforeLogin with become', async done => {
|
||||||
|
const provider = getMockFacebookProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
|
||||||
|
let hit = 0;
|
||||||
|
Parse.Cloud.beforeLogin(() => {
|
||||||
|
hit++;
|
||||||
|
});
|
||||||
|
|
||||||
|
await Parse.User._logInWith('facebook');
|
||||||
|
const sessionToken = Parse.User.current().getSessionToken();
|
||||||
|
await Parse.User.become(sessionToken);
|
||||||
|
expect(hit).toBe(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
it('cannot save non-authed user', async done => {
|
it('cannot save non-authed user', async done => {
|
||||||
let user = new Parse.User();
|
let user = new Parse.User();
|
||||||
user.set({
|
user.set({
|
||||||
@@ -1403,6 +1419,84 @@ describe('Parse.User testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('signup with provider should not call beforeLogin trigger', async done => {
|
||||||
|
const provider = getMockFacebookProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
|
||||||
|
let hit = 0;
|
||||||
|
Parse.Cloud.beforeLogin(() => {
|
||||||
|
hit++;
|
||||||
|
});
|
||||||
|
|
||||||
|
await Parse.User._logInWith('facebook');
|
||||||
|
expect(hit).toBe(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('login with provider should call beforeLogin trigger', async done => {
|
||||||
|
const provider = getMockFacebookProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
|
||||||
|
let hit = 0;
|
||||||
|
Parse.Cloud.beforeLogin(req => {
|
||||||
|
hit++;
|
||||||
|
expect(req.object.get('authData')).toBeDefined();
|
||||||
|
expect(req.object.get('name')).toBe('tupac shakur');
|
||||||
|
});
|
||||||
|
await Parse.User._logInWith('facebook');
|
||||||
|
await Parse.User.current().save({ name: 'tupac shakur' });
|
||||||
|
await Parse.User.logOut();
|
||||||
|
await Parse.User._logInWith('facebook');
|
||||||
|
expect(hit).toBe(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('incorrect login with provider should not call beforeLogin trigger', async done => {
|
||||||
|
const provider = getMockFacebookProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
|
||||||
|
let hit = 0;
|
||||||
|
Parse.Cloud.beforeLogin(() => {
|
||||||
|
hit++;
|
||||||
|
});
|
||||||
|
await Parse.User._logInWith('facebook');
|
||||||
|
await Parse.User.logOut();
|
||||||
|
provider.shouldError = true;
|
||||||
|
try {
|
||||||
|
await Parse.User._logInWith('facebook');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).toBeDefined();
|
||||||
|
}
|
||||||
|
expect(hit).toBe(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('login with provider should be blockable by beforeLogin', async done => {
|
||||||
|
const provider = getMockFacebookProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
|
||||||
|
let hit = 0;
|
||||||
|
Parse.Cloud.beforeLogin(req => {
|
||||||
|
hit++;
|
||||||
|
if (req.object.get('isBanned')) {
|
||||||
|
throw new Error('banned account');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Parse.User._logInWith('facebook');
|
||||||
|
await Parse.User.current().save({ isBanned: true });
|
||||||
|
await Parse.User.logOut();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Parse.User._logInWith('facebook');
|
||||||
|
throw new Error('should not have continued login.');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('banned account');
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(hit).toBe(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
it('link with provider', async done => {
|
it('link with provider', async done => {
|
||||||
const provider = getMockFacebookProvider();
|
const provider = getMockFacebookProvider();
|
||||||
Parse.User._registerAuthenticationProvider(provider);
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ RestWrite.prototype.execute = function() {
|
|||||||
return this.validateAuthData();
|
return this.validateAuthData();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.runBeforeTrigger();
|
return this.runBeforeSaveTrigger();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.deleteEmailResetTokenIfNeeded();
|
return this.deleteEmailResetTokenIfNeeded();
|
||||||
@@ -123,7 +123,7 @@ RestWrite.prototype.execute = function() {
|
|||||||
return this.handleFollowup();
|
return this.handleFollowup();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.runAfterTrigger();
|
return this.runAfterSaveTrigger();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.cleanUserAuthData();
|
return this.cleanUserAuthData();
|
||||||
@@ -190,7 +190,7 @@ RestWrite.prototype.validateSchema = function() {
|
|||||||
|
|
||||||
// Runs any beforeSave triggers against this operation.
|
// Runs any beforeSave triggers against this operation.
|
||||||
// Any change leads to our data being mutated.
|
// Any change leads to our data being mutated.
|
||||||
RestWrite.prototype.runBeforeTrigger = function() {
|
RestWrite.prototype.runBeforeSaveTrigger = function() {
|
||||||
if (this.response) {
|
if (this.response) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -251,6 +251,33 @@ RestWrite.prototype.runBeforeTrigger = function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RestWrite.prototype.runBeforeLoginTrigger = async function(userData) {
|
||||||
|
// Avoid doing any setup for triggers if there is no 'beforeLogin' trigger
|
||||||
|
if (
|
||||||
|
!triggers.triggerExists(
|
||||||
|
this.className,
|
||||||
|
triggers.Types.beforeLogin,
|
||||||
|
this.config.applicationId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cloud code gets a bit of extra data for its objects
|
||||||
|
const extraData = { className: this.className };
|
||||||
|
const user = triggers.inflate(extraData, userData);
|
||||||
|
|
||||||
|
// no need to return a response
|
||||||
|
await triggers.maybeRunTrigger(
|
||||||
|
triggers.Types.beforeLogin,
|
||||||
|
this.auth,
|
||||||
|
user,
|
||||||
|
null,
|
||||||
|
this.config,
|
||||||
|
this.context
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
RestWrite.prototype.setRequiredFieldsIfNeeded = function() {
|
RestWrite.prototype.setRequiredFieldsIfNeeded = function() {
|
||||||
if (this.data) {
|
if (this.data) {
|
||||||
// Add default fields
|
// Add default fields
|
||||||
@@ -377,7 +404,7 @@ RestWrite.prototype.filteredObjectsByACL = function(objects) {
|
|||||||
|
|
||||||
RestWrite.prototype.handleAuthData = function(authData) {
|
RestWrite.prototype.handleAuthData = function(authData) {
|
||||||
let results;
|
let results;
|
||||||
return this.findUsersWithAuthData(authData).then(r => {
|
return this.findUsersWithAuthData(authData).then(async r => {
|
||||||
results = this.filteredObjectsByACL(r);
|
results = this.filteredObjectsByACL(r);
|
||||||
if (results.length > 1) {
|
if (results.length > 1) {
|
||||||
// More than 1 user with the passed id's
|
// More than 1 user with the passed id's
|
||||||
@@ -421,7 +448,12 @@ RestWrite.prototype.handleAuthData = function(authData) {
|
|||||||
response: userResult,
|
response: userResult,
|
||||||
location: this.location(),
|
location: this.location(),
|
||||||
};
|
};
|
||||||
|
// Run beforeLogin hook before storing any updates
|
||||||
|
// to authData on the db; changes to userResult
|
||||||
|
// will be ignored.
|
||||||
|
await this.runBeforeLoginTrigger(deepcopy(userResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we didn't change the auth data, just keep going
|
// If we didn't change the auth data, just keep going
|
||||||
if (!hasMutatedAuthData) {
|
if (!hasMutatedAuthData) {
|
||||||
return;
|
return;
|
||||||
@@ -430,7 +462,7 @@ RestWrite.prototype.handleAuthData = function(authData) {
|
|||||||
// that can happen when token are refreshed,
|
// that can happen when token are refreshed,
|
||||||
// We should update the token and let the user in
|
// We should update the token and let the user in
|
||||||
// We should only check the mutated keys
|
// We should only check the mutated keys
|
||||||
return this.handleAuthDataValidation(mutatedAuthData).then(() => {
|
return this.handleAuthDataValidation(mutatedAuthData).then(async () => {
|
||||||
// IF we have a response, we'll skip the database operation / beforeSave / afterSave etc...
|
// IF we have a response, we'll skip the database operation / beforeSave / afterSave etc...
|
||||||
// we need to set it up there.
|
// we need to set it up there.
|
||||||
// We are supposed to have a response only on LOGIN with authData, so we skip those
|
// We are supposed to have a response only on LOGIN with authData, so we skip those
|
||||||
@@ -441,6 +473,7 @@ RestWrite.prototype.handleAuthData = function(authData) {
|
|||||||
this.response.response.authData[provider] =
|
this.response.response.authData[provider] =
|
||||||
mutatedAuthData[provider];
|
mutatedAuthData[provider];
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run the DB update directly, as 'master'
|
// Run the DB update directly, as 'master'
|
||||||
// Just update the authData part
|
// Just update the authData part
|
||||||
// Then we're good for the user, early exit of sorts
|
// Then we're good for the user, early exit of sorts
|
||||||
@@ -1415,7 +1448,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Returns nothing - doesn't wait for the trigger.
|
// Returns nothing - doesn't wait for the trigger.
|
||||||
RestWrite.prototype.runAfterTrigger = function() {
|
RestWrite.prototype.runAfterSaveTrigger = function() {
|
||||||
if (!this.response || !this.response.response) {
|
if (!this.response || !this.response.response) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import ClassesRouter from './ClassesRouter';
|
|||||||
import rest from '../rest';
|
import rest from '../rest';
|
||||||
import Auth from '../Auth';
|
import Auth from '../Auth';
|
||||||
import passwordCrypto from '../password';
|
import passwordCrypto from '../password';
|
||||||
|
import { maybeRunTrigger, Types as TriggerTypes } from '../triggers';
|
||||||
|
|
||||||
export class UsersRouter extends ClassesRouter {
|
export class UsersRouter extends ClassesRouter {
|
||||||
className() {
|
className() {
|
||||||
@@ -202,68 +203,68 @@ export class UsersRouter extends ClassesRouter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLogIn(req) {
|
async handleLogIn(req) {
|
||||||
let user;
|
const user = await this._authenticateUserFromRequest(req);
|
||||||
return this._authenticateUserFromRequest(req)
|
|
||||||
.then(res => {
|
|
||||||
user = res;
|
|
||||||
|
|
||||||
// handle password expiry policy
|
// handle password expiry policy
|
||||||
if (
|
if (req.config.passwordPolicy && req.config.passwordPolicy.maxPasswordAge) {
|
||||||
req.config.passwordPolicy &&
|
let changedAt = user._password_changed_at;
|
||||||
req.config.passwordPolicy.maxPasswordAge
|
|
||||||
) {
|
|
||||||
let changedAt = user._password_changed_at;
|
|
||||||
|
|
||||||
if (!changedAt) {
|
if (!changedAt) {
|
||||||
// password was created before expiry policy was enabled.
|
// password was created before expiry policy was enabled.
|
||||||
// simply update _User object so that it will start enforcing from now
|
// simply update _User object so that it will start enforcing from now
|
||||||
changedAt = new Date();
|
changedAt = new Date();
|
||||||
req.config.database.update(
|
req.config.database.update(
|
||||||
'_User',
|
'_User',
|
||||||
{ username: user.username },
|
{ username: user.username },
|
||||||
{ _password_changed_at: Parse._encode(changedAt) }
|
{ _password_changed_at: Parse._encode(changedAt) }
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// check whether the password has expired
|
// check whether the password has expired
|
||||||
if (changedAt.__type == 'Date') {
|
if (changedAt.__type == 'Date') {
|
||||||
changedAt = new Date(changedAt.iso);
|
changedAt = new Date(changedAt.iso);
|
||||||
}
|
|
||||||
// Calculate the expiry time.
|
|
||||||
const expiresAt = new Date(
|
|
||||||
changedAt.getTime() +
|
|
||||||
86400000 * req.config.passwordPolicy.maxPasswordAge
|
|
||||||
);
|
|
||||||
if (expiresAt < new Date())
|
|
||||||
// fail of current time is past password expiry time
|
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.OBJECT_NOT_FOUND,
|
|
||||||
'Your password has expired. Please reset your password.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Calculate the expiry time.
|
||||||
|
const expiresAt = new Date(
|
||||||
|
changedAt.getTime() +
|
||||||
|
86400000 * req.config.passwordPolicy.maxPasswordAge
|
||||||
|
);
|
||||||
|
if (expiresAt < new Date())
|
||||||
|
// fail of current time is past password expiry time
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
'Your password has expired. Please reset your password.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove hidden properties.
|
// Remove hidden properties.
|
||||||
UsersRouter.removeHiddenProperties(user);
|
UsersRouter.removeHiddenProperties(user);
|
||||||
|
|
||||||
const { sessionData, createSession } = Auth.createSession(req.config, {
|
// Before login trigger; throws if failure
|
||||||
userId: user.objectId,
|
await maybeRunTrigger(
|
||||||
createdWith: {
|
TriggerTypes.beforeLogin,
|
||||||
action: 'login',
|
req.auth,
|
||||||
authProvider: 'password',
|
Parse.User.fromJSON(Object.assign({ className: '_User' }, user)),
|
||||||
},
|
null,
|
||||||
installationId: req.info.installationId,
|
req.config
|
||||||
});
|
);
|
||||||
|
|
||||||
user.sessionToken = sessionData.sessionToken;
|
const { sessionData, createSession } = Auth.createSession(req.config, {
|
||||||
|
userId: user.objectId,
|
||||||
|
createdWith: {
|
||||||
|
action: 'login',
|
||||||
|
authProvider: 'password',
|
||||||
|
},
|
||||||
|
installationId: req.info.installationId,
|
||||||
|
});
|
||||||
|
|
||||||
req.config.filesController.expandFilesInObject(req.config, user);
|
user.sessionToken = sessionData.sessionToken;
|
||||||
|
|
||||||
return createSession();
|
req.config.filesController.expandFilesInObject(req.config, user);
|
||||||
})
|
|
||||||
.then(() => {
|
await createSession();
|
||||||
return { response: user };
|
return { response: user };
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleVerifyPassword(req) {
|
handleVerifyPassword(req) {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { Parse } from 'parse/node';
|
import { Parse } from 'parse/node';
|
||||||
import * as triggers from '../triggers';
|
import * as triggers from '../triggers';
|
||||||
|
|
||||||
|
function isParseObjectConstructor(object) {
|
||||||
|
return typeof object === 'function' && object.hasOwnProperty('className');
|
||||||
|
}
|
||||||
|
|
||||||
function getClassName(parseClass) {
|
function getClassName(parseClass) {
|
||||||
if (parseClass && parseClass.className) {
|
if (parseClass && parseClass.className) {
|
||||||
return parseClass.className;
|
return parseClass.className;
|
||||||
@@ -119,6 +123,45 @@ ParseCloud.beforeDelete = function(parseClass, handler) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Registers the before login function.
|
||||||
|
*
|
||||||
|
* **Available in Cloud Code only.**
|
||||||
|
*
|
||||||
|
* This function provides further control
|
||||||
|
* in validating a login attempt. Specifically,
|
||||||
|
* it is triggered after a user enters
|
||||||
|
* correct credentials (or other valid authData),
|
||||||
|
* but prior to a session being generated.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* Parse.Cloud.beforeLogin((request) => {
|
||||||
|
* // code here
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @method beforeLogin
|
||||||
|
* @name Parse.Cloud.beforeLogin
|
||||||
|
* @param {Function} func The function to run before a login. This function can be async and should take one parameter a {@link Parse.Cloud.TriggerRequest};
|
||||||
|
*/
|
||||||
|
ParseCloud.beforeLogin = function(handler) {
|
||||||
|
let className = '_User';
|
||||||
|
if (typeof handler === 'string' || isParseObjectConstructor(handler)) {
|
||||||
|
// validation will occur downstream, this is to maintain internal
|
||||||
|
// code consistency with the other hook types.
|
||||||
|
className = getClassName(handler);
|
||||||
|
handler = arguments[1];
|
||||||
|
}
|
||||||
|
triggers.addTrigger(
|
||||||
|
triggers.Types.beforeLogin,
|
||||||
|
className,
|
||||||
|
handler,
|
||||||
|
Parse.applicationId
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers an after save function.
|
* Registers an after save function.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Parse from 'parse/node';
|
|||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
|
||||||
export const Types = {
|
export const Types = {
|
||||||
|
beforeLogin: 'beforeLogin',
|
||||||
beforeSave: 'beforeSave',
|
beforeSave: 'beforeSave',
|
||||||
afterSave: 'afterSave',
|
afterSave: 'afterSave',
|
||||||
beforeDelete: 'beforeDelete',
|
beforeDelete: 'beforeDelete',
|
||||||
@@ -41,6 +42,11 @@ function validateClassNameForTriggers(className, type) {
|
|||||||
// TODO: Allow proper documented way of using nested increment ops
|
// TODO: Allow proper documented way of using nested increment ops
|
||||||
throw 'Only afterSave is allowed on _PushStatus';
|
throw 'Only afterSave is allowed on _PushStatus';
|
||||||
}
|
}
|
||||||
|
if (type === Types.beforeLogin && className !== '_User') {
|
||||||
|
// TODO: check if upstream code will handle `Error` instance rather
|
||||||
|
// than this anti-pattern of throwing strings
|
||||||
|
throw 'Only the _User class is allowed for the beforeLogin trigger';
|
||||||
|
}
|
||||||
return className;
|
return className;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user