feat: disableLocalStrategy with auth fields still enabled (#9579)

Adds configuration options to `auth.disableLocalStrategy` to allow
customization of how payload treats an auth enabled collection.

Two new properties have been added to `disableLocalStrategy`:

- `enableFields` Include auth fields on the collection even though the
local strategy is disabled. Useful when you do not want the database or
types to vary depending on the auth configuration used.
- `optionalPassword`: makes the password field not required
This commit is contained in:
Dan Ribbens
2024-12-03 09:52:23 -05:00
committed by GitHub
parent 40f5c72e8d
commit 6104fe5011
16 changed files with 256 additions and 41 deletions

View File

@@ -6,7 +6,13 @@ import { v4 as uuid } from 'uuid'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
import { apiKeysSlug, namedSaveToJWTValue, saveToJWTKey, slug } from './shared.js'
import {
apiKeysSlug,
namedSaveToJWTValue,
partialDisableLocaleStrategiesSlug,
saveToJWTKey,
slug,
} from './shared.js'
export default buildConfigWithDefaults({
admin: {
@@ -174,6 +180,25 @@ export default buildConfigWithDefaults({
},
],
},
{
slug: partialDisableLocaleStrategiesSlug,
auth: {
disableLocalStrategy: {
// optionalPassword: true,
enableFields: true,
},
},
fields: [
// with `enableFields: true`, the following DB columns will be created:
// email
// reset_password_token
// reset_password_expiration
// salt
// hash
// login_attempts
// lock_until
],
},
{
slug: apiKeysSlug,
access: {

View File

@@ -4,7 +4,6 @@ import type { SanitizedConfig } from 'payload'
import { expect, test } from '@playwright/test'
import { devUser } from 'credentials.js'
import path from 'path'
import { wait } from 'payload/shared'
import { fileURLToPath } from 'url'
import { v4 as uuid } from 'uuid'

View File

@@ -1,4 +1,4 @@
import type { Payload, User } from 'payload'
import type { FieldAffectingData, Payload, User } from 'payload'
import { jwtDecode } from 'jwt-decode'
import path from 'path'
@@ -9,7 +9,13 @@ import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import { devUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
import { apiKeysSlug, namedSaveToJWTValue, saveToJWTKey, slug } from './shared.js'
import {
apiKeysSlug,
namedSaveToJWTValue,
partialDisableLocaleStrategiesSlug,
saveToJWTKey,
slug,
} from './shared.js'
let restClient: NextRESTClient
let payload: Payload
@@ -709,6 +715,70 @@ describe('Auth', () => {
})
})
describe('disableLocalStrategy', () => {
it('should allow create of a user with disableLocalStrategy', async () => {
const email = 'test@example.com'
const user = await payload.create({
collection: partialDisableLocaleStrategiesSlug,
data: {
email,
// password is not required
},
})
expect(user.email).toStrictEqual(email)
})
it('should retain fields when auth.disableLocalStrategy.enableFields is true', () => {
const authFields = payload.collections[partialDisableLocaleStrategiesSlug].config.fields
// eslint-disable-next-line jest/no-conditional-in-test
.filter((field) => 'name' in field && field.name)
.map((field) => (field as FieldAffectingData).name)
expect(authFields).toMatchObject([
'updatedAt',
'createdAt',
'email',
'resetPasswordToken',
'resetPasswordExpiration',
'salt',
'hash',
'loginAttempts',
'lockUntil',
])
})
it('should prevent login of user with disableLocalStrategy.', async () => {
await payload.create({
collection: partialDisableLocaleStrategiesSlug,
data: {
email: devUser.email,
password: devUser.password,
},
})
await expect(async () => {
await payload.login({
collection: partialDisableLocaleStrategiesSlug,
data: {
email: devUser.email,
password: devUser.password,
},
})
}).rejects.toThrow('You are not allowed to perform this action.')
})
it('rest - should prevent login', async () => {
const response = await restClient.POST(`/${partialDisableLocaleStrategiesSlug}/login`, {
body: JSON.stringify({
email,
password,
}),
})
expect(response.status).toBe(403)
})
})
describe('API Key', () => {
it('should authenticate via the correct API key user', async () => {
const usersQuery = await payload.find({

View File

@@ -9,11 +9,13 @@
export interface Config {
auth: {
users: UserAuthOperations;
'partial-disable-locale-strategies': PartialDisableLocaleStrategyAuthOperations;
'api-keys': ApiKeyAuthOperations;
'public-users': PublicUserAuthOperations;
};
collections: {
users: User;
'partial-disable-locale-strategies': PartialDisableLocaleStrategy;
'api-keys': ApiKey;
'public-users': PublicUser;
relationsCollection: RelationsCollection;
@@ -24,6 +26,7 @@ export interface Config {
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
'partial-disable-locale-strategies': PartialDisableLocaleStrategiesSelect<false> | PartialDisableLocaleStrategiesSelect<true>;
'api-keys': ApiKeysSelect<false> | ApiKeysSelect<true>;
'public-users': PublicUsersSelect<false> | PublicUsersSelect<true>;
relationsCollection: RelationsCollectionSelect<false> | RelationsCollectionSelect<true>;
@@ -32,7 +35,7 @@ export interface Config {
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
defaultIDType: number;
};
globals: {};
globalsSelect: {};
@@ -41,6 +44,9 @@ export interface Config {
| (User & {
collection: 'users';
})
| (PartialDisableLocaleStrategy & {
collection: 'partial-disable-locale-strategies';
})
| (ApiKey & {
collection: 'api-keys';
})
@@ -70,6 +76,24 @@ export interface UserAuthOperations {
password: string;
};
}
export interface PartialDisableLocaleStrategyAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
export interface ApiKeyAuthOperations {
forgotPassword: {
email: string;
@@ -111,7 +135,7 @@ export interface PublicUserAuthOperations {
* via the `definition` "users".
*/
export interface User {
id: string;
id: number;
adminOnlyField?: string | null;
roles: ('admin' | 'editor' | 'moderator' | 'user' | 'viewer')[];
namedSaveToJWT?: string | null;
@@ -146,12 +170,29 @@ export interface User {
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "partial-disable-locale-strategies".
*/
export interface PartialDisableLocaleStrategy {
id: number;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "api-keys".
*/
export interface ApiKey {
id: string;
id: number;
updatedAt: string;
createdAt: string;
enableAPIKey?: boolean | null;
@@ -163,7 +204,7 @@ export interface ApiKey {
* via the `definition` "public-users".
*/
export interface PublicUser {
id: string;
id: number;
updatedAt: string;
createdAt: string;
email: string;
@@ -182,8 +223,8 @@ export interface PublicUser {
* via the `definition` "relationsCollection".
*/
export interface RelationsCollection {
id: string;
rel?: (string | null) | User;
id: number;
rel?: (number | null) | User;
text?: string | null;
updatedAt: string;
createdAt: string;
@@ -193,37 +234,45 @@ export interface RelationsCollection {
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
id: number;
document?:
| ({
relationTo: 'users';
value: string | User;
value: number | User;
} | null)
| ({
relationTo: 'partial-disable-locale-strategies';
value: number | PartialDisableLocaleStrategy;
} | null)
| ({
relationTo: 'api-keys';
value: string | ApiKey;
value: number | ApiKey;
} | null)
| ({
relationTo: 'public-users';
value: string | PublicUser;
value: number | PublicUser;
} | null)
| ({
relationTo: 'relationsCollection';
value: string | RelationsCollection;
value: number | RelationsCollection;
} | null);
globalSlug?: string | null;
user:
| {
relationTo: 'users';
value: string | User;
value: number | User;
}
| {
relationTo: 'partial-disable-locale-strategies';
value: number | PartialDisableLocaleStrategy;
}
| {
relationTo: 'api-keys';
value: string | ApiKey;
value: number | ApiKey;
}
| {
relationTo: 'public-users';
value: string | PublicUser;
value: number | PublicUser;
};
updatedAt: string;
createdAt: string;
@@ -233,19 +282,23 @@ export interface PayloadLockedDocument {
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
id: number;
user:
| {
relationTo: 'users';
value: string | User;
value: number | User;
}
| {
relationTo: 'partial-disable-locale-strategies';
value: number | PartialDisableLocaleStrategy;
}
| {
relationTo: 'api-keys';
value: string | ApiKey;
value: number | ApiKey;
}
| {
relationTo: 'public-users';
value: string | PublicUser;
value: number | PublicUser;
};
key?: string | null;
value?:
@@ -265,7 +318,7 @@ export interface PayloadPreference {
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
id: number;
name?: string | null;
batch?: number | null;
updatedAt: string;
@@ -317,6 +370,21 @@ export interface UsersSelect<T extends boolean = true> {
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "partial-disable-locale-strategies_select".
*/
export interface PartialDisableLocaleStrategiesSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "api-keys_select".

View File

@@ -1,6 +1,8 @@
export const slug = 'users'
export const apiKeysSlug = 'api-keys'
export const partialDisableLocaleStrategiesSlug = 'partial-disable-locale-strategies'
export const namedSaveToJWTValue = 'namedSaveToJWT value'
export const saveToJWTKey = 'x-custom-jwt-property-name'