feat: pass payload and names to custom auth strategies (#781)
This commit is contained in:
@@ -2,6 +2,7 @@ import { Strategy } from 'passport';
|
||||
import { DeepRequired } from 'ts-essentials';
|
||||
import { PayloadRequest } from '../express/types';
|
||||
import { Where, PayloadMongooseDocument } from '../types';
|
||||
import { Payload } from '..';
|
||||
|
||||
export type Permission = {
|
||||
permission: boolean
|
||||
@@ -67,6 +68,8 @@ type GenerateVerifyEmailSubject = (args: { req: PayloadRequest, token: string, u
|
||||
type GenerateForgotPasswordEmailHTML = (args?: { req?: PayloadRequest, token?: string, user?: unknown}) => Promise<string> | string
|
||||
type GenerateForgotPasswordEmailSubject = (args?: { req?: PayloadRequest, token?: string, user?: any }) => Promise<string> | string
|
||||
|
||||
type AuthStrategy = (ctx: Payload) => Strategy | Strategy;
|
||||
|
||||
export interface IncomingAuthType {
|
||||
tokenExpiration?: number;
|
||||
verify?:
|
||||
@@ -90,7 +93,8 @@ export interface IncomingAuthType {
|
||||
}
|
||||
disableLocalStrategy?: true
|
||||
strategies?: {
|
||||
strategy: Strategy
|
||||
name?: string
|
||||
strategy: AuthStrategy
|
||||
refresh?: boolean
|
||||
logout?: boolean
|
||||
}[]
|
||||
|
||||
@@ -89,7 +89,11 @@ const collectionSchema = joi.object().keys({
|
||||
maxLoginAttempts: joi.number(),
|
||||
disableLocalStrategy: joi.boolean().valid(true),
|
||||
strategies: joi.array().items(joi.object().keys({
|
||||
strategy: joi.object().required(),
|
||||
name: joi.string(),
|
||||
strategy: joi.alternatives().try(
|
||||
joi.func().maxArity(1).required(),
|
||||
joi.object()
|
||||
),
|
||||
refresh: joi.boolean(),
|
||||
logout: joi.boolean(),
|
||||
})),
|
||||
|
||||
@@ -106,8 +106,9 @@ export default function registerCollections(ctx: Payload): void {
|
||||
}
|
||||
|
||||
if (Array.isArray(collection.auth.strategies)) {
|
||||
collection.auth.strategies.forEach(({ strategy }) => {
|
||||
passport.use(strategy);
|
||||
collection.auth.strategies.forEach(({ name, strategy }, index) => {
|
||||
const passportStrategy = typeof strategy === 'object' ? strategy : strategy(ctx);
|
||||
passport.use(`${AuthCollection.config.slug}-${name ?? index}`, passportStrategy);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ export default (config: SanitizedConfig): PayloadAuthenticate => {
|
||||
const collectionMethods = [...enabledMethods];
|
||||
|
||||
if (Array.isArray(collection.auth.strategies)) {
|
||||
collection.auth.strategies.forEach(({ strategy }) => {
|
||||
collectionMethods.unshift(strategy.name);
|
||||
collection.auth.strategies.forEach(({ name }, index) => {
|
||||
collectionMethods.unshift(`${collection.slug}-${name ?? index}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
96
test/auth/custom-strategy/config.ts
Normal file
96
test/auth/custom-strategy/config.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { Request } from 'express';
|
||||
import { Strategy } from 'passport-strategy';
|
||||
import { Payload } from '../../../src';
|
||||
import { buildConfig } from '../../buildConfig';
|
||||
|
||||
export const slug = 'users';
|
||||
export const strategyName = 'test-local'
|
||||
|
||||
export class CustomStrategy extends Strategy {
|
||||
|
||||
ctx: Payload;
|
||||
|
||||
constructor(ctx: Payload) {
|
||||
super();
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
authenticate(req: Request, options?: any): void {
|
||||
if(!req.headers.code && !req.headers.secret) {
|
||||
return this.success(null);
|
||||
}
|
||||
this.ctx.find({
|
||||
collection: slug,
|
||||
where: {
|
||||
code: {
|
||||
equals: req.headers.code
|
||||
},
|
||||
secret: {
|
||||
equals: req.headers.secret
|
||||
}
|
||||
}
|
||||
}).then((users) => {
|
||||
if(users.docs && users.docs.length) {
|
||||
const user = users.docs[0];
|
||||
user.collection = slug;
|
||||
user._strategy = `${slug}-${strategyName}`;
|
||||
this.success(user)
|
||||
} else {
|
||||
this.error(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default buildConfig({
|
||||
admin: {
|
||||
user: 'users',
|
||||
},
|
||||
collections: [
|
||||
{
|
||||
slug,
|
||||
auth: {
|
||||
disableLocalStrategy: true,
|
||||
strategies: [
|
||||
{
|
||||
name: strategyName,
|
||||
strategy: (ctx) => new CustomStrategy(ctx)
|
||||
}
|
||||
]
|
||||
},
|
||||
access: {
|
||||
create: () => true
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'code',
|
||||
label: 'Code',
|
||||
type: 'text',
|
||||
unique: true,
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
name: 'secret',
|
||||
label: 'Secret',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'roles',
|
||||
label: 'Role',
|
||||
type: 'select',
|
||||
options: ['admin', 'editor', 'moderator', 'user', 'viewer'],
|
||||
defaultValue: 'user',
|
||||
required: true,
|
||||
saveToJWT: true,
|
||||
hasMany: true,
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
57
test/auth/custom-strategy/int.spec.ts
Normal file
57
test/auth/custom-strategy/int.spec.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import mongoose from 'mongoose';
|
||||
import payload from '../../../src';
|
||||
import { initPayloadTest } from '../../helpers/configHelpers';
|
||||
import { slug } from './config';
|
||||
|
||||
require('isomorphic-fetch');
|
||||
|
||||
let apiUrl;
|
||||
|
||||
const [ code, secret, name ] = [ 'test', 'strategy', 'Tester' ];
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
describe('AuthStrategies', () => {
|
||||
beforeAll(async () => {
|
||||
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
|
||||
apiUrl = `${serverURL}/api`;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await mongoose.connection.dropDatabase();
|
||||
await mongoose.connection.close();
|
||||
await payload.mongoMemoryServer.stop();
|
||||
});
|
||||
|
||||
describe('create user', () => {
|
||||
beforeAll(async () => {
|
||||
await fetch(`${apiUrl}/${slug}`, {
|
||||
body: JSON.stringify({
|
||||
code,
|
||||
secret,
|
||||
name
|
||||
}),
|
||||
headers,
|
||||
method: 'post',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a logged in user from /me', async () => {
|
||||
const response = await fetch(`${apiUrl}/${slug}/me`, {
|
||||
headers: {
|
||||
...headers,
|
||||
code,
|
||||
secret
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.user.name).toBe(name)
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user