feat: added beforeLogin hook (#1289)

This commit is contained in:
Daniel Söderling
2022-10-24 18:05:12 +02:00
committed by GitHub
parent a9f2f0ec03
commit 09d793926d
7 changed files with 89 additions and 5 deletions

View File

@@ -190,7 +190,7 @@ const afterDeleteHook: CollectionAfterDeleteHook = async ({
### 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
import { CollectionBeforeLoginHook } from 'payload/types';
@@ -198,7 +198,6 @@ import { CollectionBeforeLoginHook } from 'payload/types';
const beforeLoginHook: CollectionBeforeLoginHook = async ({
req, // full express request
user, // user being logged in
token, // user token
}) => {
return user;
}
@@ -213,6 +212,8 @@ import { CollectionAfterLoginHook } from 'payload/types';
const afterLoginHook: CollectionAfterLoginHook = async ({
req, // full express request
user, // user that was logged in
token, // user token
}) => {...}
```

View File

@@ -133,6 +133,15 @@ async function login<T>(incomingArgs: Arguments): Promise<Result & { user: T}> {
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(
fieldsToSign,
secret,
@@ -166,7 +175,7 @@ async function login<T>(incomingArgs: Arguments): Promise<Result & { user: T}> {
await priorHook;
user = await hook({
doc: user,
user,
req: args.req,
token,
}) || user;

View File

@@ -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 BeforeLoginHook = (args: {
export type BeforeLoginHook<T extends TypeWithID = any> = (args: {
req: PayloadRequest;
user: T
}) => any;
export type AfterLoginHook<T extends TypeWithID = any> = (args: {
req: PayloadRequest;
doc: T;
user: T;
token: string;
}) => any;

View File

@@ -3,3 +3,8 @@ export const devUser = {
password: 'test',
roles: ['admin'],
};
export const regularUser = {
email: 'user@payloadcms.com',
password: 'test2',
roles: ['user'],
};

View 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;

View File

@@ -3,6 +3,7 @@ import TransformHooks from './collections/Transform';
import Hooks, { hooksSlug } from './collections/Hook';
import NestedAfterReadHooks from './collections/NestedAfterReadHooks';
import Relations from './collections/Relations';
import Users, { seedHooksUsers } from './collections/Users';
export default buildConfig({
collections: [
@@ -10,8 +11,10 @@ export default buildConfig({
Hooks,
NestedAfterReadHooks,
Relations,
Users,
],
onInit: async (payload) => {
await seedHooksUsers(payload);
await payload.create({
collection: hooksSlug,
data: {

View File

@@ -8,6 +8,9 @@ import { hooksSlug } from './collections/Hook';
import { generatedAfterReadText, nestedAfterReadHooksSlug } from './collections/NestedAfterReadHooks';
import { relationsSlug } from './collections/Relations';
import type { NestedAfterReadHook } from './payload-types';
import { hooksUsersSlug } from './collections/Users';
import { devUser, regularUser } from '../credentials';
import { AuthenticationError } from '../../src/errors';
let client: RESTClient;
@@ -117,4 +120,20 @@ describe('Hooks', () => {
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);
});
});
});