feat: added beforeLogin hook (#1289)
This commit is contained in:
@@ -190,7 +190,7 @@ const afterDeleteHook: CollectionAfterDeleteHook = async ({
|
|||||||
|
|
||||||
### beforeLogin
|
### beforeLogin
|
||||||
|
|
||||||
For auth-enabled Collections, this hook runs after successful `login` operations. You can optionally modify the user that is returned.
|
For auth-enabled Collections, this hook runs during `login` operations where a user with the provided credentials exist, but before a token is generated and added too the response. You can optionally modify the user that is returned, or throw an error in order to deny the login operation.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { CollectionBeforeLoginHook } from 'payload/types';
|
import { CollectionBeforeLoginHook } from 'payload/types';
|
||||||
@@ -198,7 +198,6 @@ import { CollectionBeforeLoginHook } from 'payload/types';
|
|||||||
const beforeLoginHook: CollectionBeforeLoginHook = async ({
|
const beforeLoginHook: CollectionBeforeLoginHook = async ({
|
||||||
req, // full express request
|
req, // full express request
|
||||||
user, // user being logged in
|
user, // user being logged in
|
||||||
token, // user token
|
|
||||||
}) => {
|
}) => {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@@ -213,6 +212,8 @@ import { CollectionAfterLoginHook } from 'payload/types';
|
|||||||
|
|
||||||
const afterLoginHook: CollectionAfterLoginHook = async ({
|
const afterLoginHook: CollectionAfterLoginHook = async ({
|
||||||
req, // full express request
|
req, // full express request
|
||||||
|
user, // user that was logged in
|
||||||
|
token, // user token
|
||||||
}) => {...}
|
}) => {...}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,15 @@ async function login<T>(incomingArgs: Arguments): Promise<Result & { user: T}> {
|
|||||||
collection: collectionConfig.slug,
|
collection: collectionConfig.slug,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await collectionConfig.hooks.beforeLogin.reduce(async (priorHook, hook) => {
|
||||||
|
await priorHook;
|
||||||
|
|
||||||
|
user = (await hook({
|
||||||
|
user,
|
||||||
|
req: args.req,
|
||||||
|
})) || user;
|
||||||
|
}, Promise.resolve());
|
||||||
|
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
fieldsToSign,
|
fieldsToSign,
|
||||||
secret,
|
secret,
|
||||||
@@ -166,7 +175,7 @@ async function login<T>(incomingArgs: Arguments): Promise<Result & { user: T}> {
|
|||||||
await priorHook;
|
await priorHook;
|
||||||
|
|
||||||
user = await hook({
|
user = await hook({
|
||||||
doc: user,
|
user,
|
||||||
req: args.req,
|
req: args.req,
|
||||||
token,
|
token,
|
||||||
}) || user;
|
}) || user;
|
||||||
|
|||||||
@@ -112,13 +112,14 @@ export type AfterDeleteHook<T extends TypeWithID = any> = (args: {
|
|||||||
|
|
||||||
export type AfterErrorHook = (err: Error, res: unknown) => { response: any, status: number } | void;
|
export type AfterErrorHook = (err: Error, res: unknown) => { response: any, status: number } | void;
|
||||||
|
|
||||||
export type BeforeLoginHook = (args: {
|
export type BeforeLoginHook<T extends TypeWithID = any> = (args: {
|
||||||
req: PayloadRequest;
|
req: PayloadRequest;
|
||||||
|
user: T
|
||||||
}) => any;
|
}) => any;
|
||||||
|
|
||||||
export type AfterLoginHook<T extends TypeWithID = any> = (args: {
|
export type AfterLoginHook<T extends TypeWithID = any> = (args: {
|
||||||
req: PayloadRequest;
|
req: PayloadRequest;
|
||||||
doc: T;
|
user: T;
|
||||||
token: string;
|
token: string;
|
||||||
}) => any;
|
}) => any;
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,8 @@ export const devUser = {
|
|||||||
password: 'test',
|
password: 'test',
|
||||||
roles: ['admin'],
|
roles: ['admin'],
|
||||||
};
|
};
|
||||||
|
export const regularUser = {
|
||||||
|
email: 'user@payloadcms.com',
|
||||||
|
password: 'test2',
|
||||||
|
roles: ['user'],
|
||||||
|
};
|
||||||
|
|||||||
46
test/hooks/collections/Users/index.ts
Normal file
46
test/hooks/collections/Users/index.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Payload } from '../../../../src';
|
||||||
|
import { BeforeLoginHook, CollectionConfig } from '../../../../src/collections/config/types';
|
||||||
|
import { AuthenticationError } from '../../../../src/errors';
|
||||||
|
import { devUser, regularUser } from '../../../credentials';
|
||||||
|
|
||||||
|
const beforeLoginHook: BeforeLoginHook = ({ user }) => {
|
||||||
|
const isAdmin = user.roles.includes('admin') ? user : undefined;
|
||||||
|
if (!isAdmin) {
|
||||||
|
throw new AuthenticationError();
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const seedHooksUsers = async (payload: Payload) => {
|
||||||
|
await payload.create({
|
||||||
|
collection: hooksUsersSlug,
|
||||||
|
data: devUser,
|
||||||
|
});
|
||||||
|
await payload.create({
|
||||||
|
collection: hooksUsersSlug,
|
||||||
|
data: regularUser,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hooksUsersSlug = 'hooks-users';
|
||||||
|
const Users: CollectionConfig = {
|
||||||
|
slug: hooksUsersSlug,
|
||||||
|
auth: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'roles',
|
||||||
|
label: 'Role',
|
||||||
|
type: 'select',
|
||||||
|
options: ['admin', 'user'],
|
||||||
|
defaultValue: 'user',
|
||||||
|
required: true,
|
||||||
|
saveToJWT: true,
|
||||||
|
hasMany: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hooks: {
|
||||||
|
beforeLogin: [beforeLoginHook],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Users;
|
||||||
@@ -3,6 +3,7 @@ import TransformHooks from './collections/Transform';
|
|||||||
import Hooks, { hooksSlug } from './collections/Hook';
|
import Hooks, { hooksSlug } from './collections/Hook';
|
||||||
import NestedAfterReadHooks from './collections/NestedAfterReadHooks';
|
import NestedAfterReadHooks from './collections/NestedAfterReadHooks';
|
||||||
import Relations from './collections/Relations';
|
import Relations from './collections/Relations';
|
||||||
|
import Users, { seedHooksUsers } from './collections/Users';
|
||||||
|
|
||||||
export default buildConfig({
|
export default buildConfig({
|
||||||
collections: [
|
collections: [
|
||||||
@@ -10,8 +11,10 @@ export default buildConfig({
|
|||||||
Hooks,
|
Hooks,
|
||||||
NestedAfterReadHooks,
|
NestedAfterReadHooks,
|
||||||
Relations,
|
Relations,
|
||||||
|
Users,
|
||||||
],
|
],
|
||||||
onInit: async (payload) => {
|
onInit: async (payload) => {
|
||||||
|
await seedHooksUsers(payload);
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: hooksSlug,
|
collection: hooksSlug,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import { hooksSlug } from './collections/Hook';
|
|||||||
import { generatedAfterReadText, nestedAfterReadHooksSlug } from './collections/NestedAfterReadHooks';
|
import { generatedAfterReadText, nestedAfterReadHooksSlug } from './collections/NestedAfterReadHooks';
|
||||||
import { relationsSlug } from './collections/Relations';
|
import { relationsSlug } from './collections/Relations';
|
||||||
import type { NestedAfterReadHook } from './payload-types';
|
import type { NestedAfterReadHook } from './payload-types';
|
||||||
|
import { hooksUsersSlug } from './collections/Users';
|
||||||
|
import { devUser, regularUser } from '../credentials';
|
||||||
|
import { AuthenticationError } from '../../src/errors';
|
||||||
|
|
||||||
let client: RESTClient;
|
let client: RESTClient;
|
||||||
|
|
||||||
@@ -117,4 +120,20 @@ describe('Hooks', () => {
|
|||||||
expect(retrievedDoc.group.subGroup.shouldPopulate.title).toEqual(relation.title);
|
expect(retrievedDoc.group.subGroup.shouldPopulate.title).toEqual(relation.title);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('auth collection hooks', () => {
|
||||||
|
it('allow admin login', async () => {
|
||||||
|
const { user } = await payload.login({
|
||||||
|
collection: hooksUsersSlug,
|
||||||
|
data: {
|
||||||
|
email: devUser.email,
|
||||||
|
password: devUser.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(user).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deny user login', async () => {
|
||||||
|
await expect(() => payload.login({ collection: hooksUsersSlug, data: { email: regularUser.email, password: regularUser.password } })).rejects.toThrow(AuthenticationError);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user