Skip to content

Commit 3a3026c

Browse files
feat: pass payload and names to custom auth strategies (#781)
1 parent 2a1f387 commit 3a3026c

File tree

6 files changed

+168
-6
lines changed

6 files changed

+168
-6
lines changed

src/auth/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Strategy } from 'passport';
22
import { DeepRequired } from 'ts-essentials';
33
import { PayloadRequest } from '../express/types';
44
import { Where, PayloadMongooseDocument } from '../types';
5+
import { Payload } from '..';
56

67
export type Permission = {
78
permission: boolean
@@ -67,6 +68,8 @@ type GenerateVerifyEmailSubject = (args: { req: PayloadRequest, token: string, u
6768
type GenerateForgotPasswordEmailHTML = (args?: { req?: PayloadRequest, token?: string, user?: unknown}) => Promise<string> | string
6869
type GenerateForgotPasswordEmailSubject = (args?: { req?: PayloadRequest, token?: string, user?: any }) => Promise<string> | string
6970

71+
type AuthStrategy = (ctx: Payload) => Strategy | Strategy;
72+
7073
export interface IncomingAuthType {
7174
tokenExpiration?: number;
7275
verify?:
@@ -90,7 +93,8 @@ export interface IncomingAuthType {
9093
}
9194
disableLocalStrategy?: true
9295
strategies?: {
93-
strategy: Strategy
96+
name?: string
97+
strategy: AuthStrategy
9498
refresh?: boolean
9599
logout?: boolean
96100
}[]

src/collections/config/schema.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,11 @@ const collectionSchema = joi.object().keys({
8989
maxLoginAttempts: joi.number(),
9090
disableLocalStrategy: joi.boolean().valid(true),
9191
strategies: joi.array().items(joi.object().keys({
92-
strategy: joi.object().required(),
92+
name: joi.string(),
93+
strategy: joi.alternatives().try(
94+
joi.func().maxArity(1).required(),
95+
joi.object()
96+
),
9397
refresh: joi.boolean(),
9498
logout: joi.boolean(),
9599
})),

src/collections/init.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,9 @@ export default function registerCollections(ctx: Payload): void {
106106
}
107107

108108
if (Array.isArray(collection.auth.strategies)) {
109-
collection.auth.strategies.forEach(({ strategy }) => {
110-
passport.use(strategy);
109+
collection.auth.strategies.forEach(({ name, strategy }, index) => {
110+
const passportStrategy = typeof strategy === 'object' ? strategy : strategy(ctx);
111+
passport.use(`${AuthCollection.config.slug}-${name ?? index}`, passportStrategy);
111112
});
112113
}
113114
}

src/express/middleware/authenticate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export default (config: SanitizedConfig): PayloadAuthenticate => {
1212
const collectionMethods = [...enabledMethods];
1313

1414
if (Array.isArray(collection.auth.strategies)) {
15-
collection.auth.strategies.forEach(({ strategy }) => {
16-
collectionMethods.unshift(strategy.name);
15+
collection.auth.strategies.forEach(({ name }, index) => {
16+
collectionMethods.unshift(`${collection.slug}-${name ?? index}`);
1717
});
1818
}
1919

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Request } from 'express';
2+
import { Strategy } from 'passport-strategy';
3+
import { Payload } from '../../../src';
4+
import { buildConfig } from '../../buildConfig';
5+
6+
export const slug = 'users';
7+
export const strategyName = 'test-local'
8+
9+
export class CustomStrategy extends Strategy {
10+
11+
ctx: Payload;
12+
13+
constructor(ctx: Payload) {
14+
super();
15+
this.ctx = ctx;
16+
}
17+
18+
authenticate(req: Request, options?: any): void {
19+
if(!req.headers.code && !req.headers.secret) {
20+
return this.success(null);
21+
}
22+
this.ctx.find({
23+
collection: slug,
24+
where: {
25+
code: {
26+
equals: req.headers.code
27+
},
28+
secret: {
29+
equals: req.headers.secret
30+
}
31+
}
32+
}).then((users) => {
33+
if(users.docs && users.docs.length) {
34+
const user = users.docs[0];
35+
user.collection = slug;
36+
user._strategy = `${slug}-${strategyName}`;
37+
this.success(user)
38+
} else {
39+
this.error(null)
40+
}
41+
})
42+
}
43+
}
44+
45+
export default buildConfig({
46+
admin: {
47+
user: 'users',
48+
},
49+
collections: [
50+
{
51+
slug,
52+
auth: {
53+
disableLocalStrategy: true,
54+
strategies: [
55+
{
56+
name: strategyName,
57+
strategy: (ctx) => new CustomStrategy(ctx)
58+
}
59+
]
60+
},
61+
access: {
62+
create: () => true
63+
},
64+
fields: [
65+
{
66+
name: 'code',
67+
label: 'Code',
68+
type: 'text',
69+
unique: true,
70+
index: true,
71+
},
72+
{
73+
name: 'secret',
74+
label: 'Secret',
75+
type: 'text',
76+
},
77+
{
78+
name: 'name',
79+
label: 'Name',
80+
type: 'text',
81+
},
82+
{
83+
name: 'roles',
84+
label: 'Role',
85+
type: 'select',
86+
options: ['admin', 'editor', 'moderator', 'user', 'viewer'],
87+
defaultValue: 'user',
88+
required: true,
89+
saveToJWT: true,
90+
hasMany: true,
91+
},
92+
93+
],
94+
},
95+
],
96+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import mongoose from 'mongoose';
2+
import payload from '../../../src';
3+
import { initPayloadTest } from '../../helpers/configHelpers';
4+
import { slug } from './config';
5+
6+
require('isomorphic-fetch');
7+
8+
let apiUrl;
9+
10+
const [ code, secret, name ] = [ 'test', 'strategy', 'Tester' ];
11+
12+
const headers = {
13+
'Content-Type': 'application/json',
14+
};
15+
16+
describe('AuthStrategies', () => {
17+
beforeAll(async () => {
18+
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
19+
apiUrl = `${serverURL}/api`;
20+
});
21+
22+
afterAll(async () => {
23+
await mongoose.connection.dropDatabase();
24+
await mongoose.connection.close();
25+
await payload.mongoMemoryServer.stop();
26+
});
27+
28+
describe('create user', () => {
29+
beforeAll(async () => {
30+
await fetch(`${apiUrl}/${slug}`, {
31+
body: JSON.stringify({
32+
code,
33+
secret,
34+
name
35+
}),
36+
headers,
37+
method: 'post',
38+
});
39+
});
40+
41+
it('should return a logged in user from /me', async () => {
42+
const response = await fetch(`${apiUrl}/${slug}/me`, {
43+
headers: {
44+
...headers,
45+
code,
46+
secret
47+
},
48+
});
49+
50+
const data = await response.json();
51+
52+
expect(response.status).toBe(200);
53+
expect(data.user.name).toBe(name)
54+
});
55+
56+
});
57+
});

0 commit comments

Comments
 (0)