feat: pass payload and names to custom auth strategies (#781)

This commit is contained in:
Luciano Greiner
2022-07-21 19:18:26 -03:00
committed by GitHub
parent 2a1f387bcc
commit 3a3026cd63
6 changed files with 168 additions and 6 deletions

View File

@@ -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
}[]

View File

@@ -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(),
})),

View File

@@ -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);
});
}
}

View File

@@ -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}`);
});
}

View 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,
},
],
},
],
});

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