Skip to content

Commit 6c129da

Browse files
authored
Add new afterLogin cloud code hook (parse-community#6387)
* add new afterLogin cloud code hook * include user on req.user for afterLogin hook
1 parent 5ab6630 commit 6c129da

File tree

4 files changed

+145
-7
lines changed

4 files changed

+145
-7
lines changed

spec/CloudCode.spec.js

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2269,21 +2269,43 @@ describe('afterFind hooks', () => {
22692269
expect(() => {
22702270
Parse.Cloud.beforeLogin(() => {});
22712271
}).not.toThrow(
2272-
'Only the _User class is allowed for the beforeLogin trigger'
2272+
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
22732273
);
22742274
expect(() => {
22752275
Parse.Cloud.beforeLogin('_User', () => {});
22762276
}).not.toThrow(
2277-
'Only the _User class is allowed for the beforeLogin trigger'
2277+
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
22782278
);
22792279
expect(() => {
22802280
Parse.Cloud.beforeLogin(Parse.User, () => {});
22812281
}).not.toThrow(
2282-
'Only the _User class is allowed for the beforeLogin trigger'
2282+
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
22832283
);
22842284
expect(() => {
22852285
Parse.Cloud.beforeLogin('SomeClass', () => {});
2286-
}).toThrow('Only the _User class is allowed for the beforeLogin trigger');
2286+
}).toThrow(
2287+
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
2288+
);
2289+
expect(() => {
2290+
Parse.Cloud.afterLogin(() => {});
2291+
}).not.toThrow(
2292+
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
2293+
);
2294+
expect(() => {
2295+
Parse.Cloud.afterLogin('_User', () => {});
2296+
}).not.toThrow(
2297+
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
2298+
);
2299+
expect(() => {
2300+
Parse.Cloud.afterLogin(Parse.User, () => {});
2301+
}).not.toThrow(
2302+
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
2303+
);
2304+
expect(() => {
2305+
Parse.Cloud.afterLogin('SomeClass', () => {});
2306+
}).toThrow(
2307+
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
2308+
);
22872309
expect(() => {
22882310
Parse.Cloud.afterLogout(() => {});
22892311
}).not.toThrow();
@@ -2574,3 +2596,66 @@ describe('beforeLogin hook', () => {
25742596
expect(afterFinds).toEqual(1);
25752597
});
25762598
});
2599+
2600+
describe('afterLogin hook', () => {
2601+
it('should run afterLogin after successful login', async done => {
2602+
let hit = 0;
2603+
Parse.Cloud.afterLogin(req => {
2604+
hit++;
2605+
expect(req.object.get('username')).toEqual('testuser');
2606+
});
2607+
2608+
await Parse.User.signUp('testuser', 'p@ssword');
2609+
const user = await Parse.User.logIn('testuser', 'p@ssword');
2610+
expect(hit).toBe(1);
2611+
expect(user).toBeDefined();
2612+
expect(user.getUsername()).toBe('testuser');
2613+
expect(user.getSessionToken()).toBeDefined();
2614+
done();
2615+
});
2616+
2617+
it('should not run afterLogin after unsuccessful login', async done => {
2618+
let hit = 0;
2619+
Parse.Cloud.afterLogin(req => {
2620+
hit++;
2621+
expect(req.object.get('username')).toEqual('testuser');
2622+
});
2623+
2624+
await Parse.User.signUp('testuser', 'p@ssword');
2625+
try {
2626+
await Parse.User.logIn('testuser', 'badpassword');
2627+
} catch (e) {
2628+
expect(e.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
2629+
}
2630+
expect(hit).toBe(0);
2631+
done();
2632+
});
2633+
2634+
it('should not run afterLogin on sign up', async done => {
2635+
let hit = 0;
2636+
Parse.Cloud.afterLogin(req => {
2637+
hit++;
2638+
expect(req.object.get('username')).toEqual('testuser');
2639+
});
2640+
2641+
const user = await Parse.User.signUp('testuser', 'p@ssword');
2642+
expect(user).toBeDefined();
2643+
expect(hit).toBe(0);
2644+
done();
2645+
});
2646+
2647+
it('should have expected data in request', async done => {
2648+
Parse.Cloud.afterLogin(req => {
2649+
expect(req.object).toBeDefined();
2650+
expect(req.user).toBeDefined();
2651+
expect(req.headers).toBeDefined();
2652+
expect(req.ip).toBeDefined();
2653+
expect(req.installationId).toBeDefined();
2654+
expect(req.context).toBeUndefined();
2655+
});
2656+
2657+
await Parse.User.signUp('testuser', 'p@ssword');
2658+
await Parse.User.logIn('testuser', 'p@ssword');
2659+
done();
2660+
});
2661+
});

src/Routers/UsersRouter.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,18 @@ export class UsersRouter extends ClassesRouter {
264264
user.sessionToken = sessionData.sessionToken;
265265

266266
await createSession();
267+
268+
const afterLoginUser = Parse.User.fromJSON(
269+
Object.assign({ className: '_User' }, user)
270+
);
271+
maybeRunTrigger(
272+
TriggerTypes.afterLogin,
273+
{ ...req.auth, user: afterLoginUser },
274+
afterLoginUser,
275+
null,
276+
req.config
277+
);
278+
267279
return { response: user };
268280
}
269281

src/cloud-code/Parse.Cloud.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,42 @@ ParseCloud.beforeLogin = function(handler) {
165165
);
166166
};
167167

168+
/**
169+
*
170+
* Registers the after login function.
171+
*
172+
* **Available in Cloud Code only.**
173+
*
174+
* This function is triggered after a user logs in successfully,
175+
* and after a _Session object has been created.
176+
*
177+
* ```
178+
* Parse.Cloud.afterLogin((request) => {
179+
* // code here
180+
* })
181+
*
182+
* ```
183+
*
184+
* @method afterLogin
185+
* @name Parse.Cloud.afterLogin
186+
* @param {Function} func The function to run after a login. This function can be async and should take one parameter a {@link Parse.Cloud.TriggerRequest};
187+
*/
188+
ParseCloud.afterLogin = function(handler) {
189+
let className = '_User';
190+
if (typeof handler === 'string' || isParseObjectConstructor(handler)) {
191+
// validation will occur downstream, this is to maintain internal
192+
// code consistency with the other hook types.
193+
className = getClassName(handler);
194+
handler = arguments[1];
195+
}
196+
triggers.addTrigger(
197+
triggers.Types.afterLogin,
198+
className,
199+
handler,
200+
Parse.applicationId
201+
);
202+
};
203+
168204
/**
169205
*
170206
* Registers the after logout function.

src/triggers.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { logger } from './logger';
44

55
export const Types = {
66
beforeLogin: 'beforeLogin',
7+
afterLogin: 'afterLogin',
78
afterLogout: 'afterLogout',
89
beforeSave: 'beforeSave',
910
afterSave: 'afterSave',
@@ -39,10 +40,13 @@ function validateClassNameForTriggers(className, type) {
3940
// TODO: Allow proper documented way of using nested increment ops
4041
throw 'Only afterSave is allowed on _PushStatus';
4142
}
42-
if (type === Types.beforeLogin && className !== '_User') {
43+
if (
44+
(type === Types.beforeLogin || type === Types.afterLogin) &&
45+
className !== '_User'
46+
) {
4347
// TODO: check if upstream code will handle `Error` instance rather
4448
// than this anti-pattern of throwing strings
45-
throw 'Only the _User class is allowed for the beforeLogin trigger';
49+
throw 'Only the _User class is allowed for the beforeLogin and afterLogin triggers';
4650
}
4751
if (type === Types.afterLogout && className !== '_Session') {
4852
// TODO: check if upstream code will handle `Error` instance rather
@@ -615,7 +619,8 @@ export function maybeRunTrigger(
615619
const promise = trigger(request);
616620
if (
617621
triggerType === Types.afterSave ||
618-
triggerType === Types.afterDelete
622+
triggerType === Types.afterDelete ||
623+
triggerType === Types.afterLogin
619624
) {
620625
logTriggerAfterHook(
621626
triggerType,

0 commit comments

Comments
 (0)