-
Notifications
You must be signed in to change notification settings - Fork 4k
perf!: remove DeepRequired from sanitized collection types #17541
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,3 @@ | ||
| import type { DeepRequired } from 'ts-essentials' | ||
|
|
||
| import type { CollectionSlug, GlobalSlug, Payload, User } from '../index.js' | ||
| import type { PayloadRequest, Where } from '../types/index.js' | ||
|
|
||
|
|
@@ -216,15 +214,17 @@ export type LoginWithUsernameOptions = | |
| requireUsername?: boolean | ||
| } | ||
|
|
||
| type AuthCookies = { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just extracting it out so we dont have to do
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any benefit in exporting these? |
||
| domain?: string | ||
| sameSite?: 'Lax' | 'None' | 'Strict' | boolean | ||
| secure?: boolean | ||
| } | ||
|
|
||
| export interface IncomingAuthType { | ||
| /** | ||
| * Set cookie options, including secure, sameSite, and domain. For advanced users. | ||
| */ | ||
| cookies?: { | ||
| domain?: string | ||
| sameSite?: 'Lax' | 'None' | 'Strict' | boolean | ||
| secure?: boolean | ||
| } | ||
| cookies?: AuthCookies | ||
| /** | ||
| * How many levels deep a user document should be populated when creating the JWT and binding the user to the req. Defaults to 0 and should only be modified if absolutely necessary, as this will affect performance. | ||
| * @default 0 | ||
|
|
@@ -316,14 +316,32 @@ export type VerifyConfig = { | |
| } | ||
|
|
||
| export interface Auth | ||
| extends Omit<DeepRequired<IncomingAuthType>, 'forgotPassword' | 'loginWithUsername' | 'verify'> { | ||
| forgotPassword?: { | ||
| expiration?: number | ||
| generateEmailHTML?: GenerateForgotPasswordEmailHTML | ||
| generateEmailSubject?: GenerateForgotPasswordEmailSubject | ||
| } | ||
| loginWithUsername: false | LoginWithUsernameOptions | ||
| verify?: boolean | VerifyConfig | ||
| extends Omit< | ||
| IncomingAuthType, | ||
| | 'cookies' | ||
| | 'forgotPassword' | ||
| | 'lockTime' | ||
| | 'loginWithUsername' | ||
| | 'maxLoginAttempts' | ||
| | 'strategies' | ||
| | 'tokenExpiration' | ||
| | 'useSessions' | ||
| | 'verify' | ||
| >, | ||
| Required< | ||
| Pick< | ||
| IncomingAuthType, | ||
| | 'forgotPassword' | ||
| | 'lockTime' | ||
| | 'maxLoginAttempts' | ||
| | 'strategies' | ||
| | 'tokenExpiration' | ||
| | 'useSessions' | ||
| | 'verify' | ||
| > | ||
| > { | ||
| cookies: Pick<AuthCookies, 'domain'> & Required<Pick<AuthCookies, 'sameSite' | 'secure'>> | ||
| loginWithUsername: false | Required<LoginWithUsernameOptions> | ||
| } | ||
|
|
||
| export function hasWhereAccessResult(result: boolean | Where): result is Where { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import type { IncomingAuthType, LoginWithUsernameOptions } from '../../auth/types.js' | ||
| import type { CollectionConfig } from './types.js' | ||
| import type { Auth, IncomingAuthType, LoginWithUsernameOptions } from '../../auth/types.js' | ||
| import type { CollectionConfig, SanitizedCollectionConfig } from './types.js' | ||
|
|
||
| import { defaultAccess } from '../../auth/defaultAccess.js' | ||
|
|
||
|
|
@@ -32,6 +32,7 @@ export const defaults: Partial<CollectionConfig> = { | |
| hooks: { | ||
| afterChange: [], | ||
| afterDelete: [], | ||
| afterError: [], | ||
| afterForgotPassword: [], | ||
| afterLogin: [], | ||
| afterLogout: [], | ||
|
|
@@ -55,14 +56,16 @@ export const defaults: Partial<CollectionConfig> = { | |
| } | ||
|
|
||
| export const addDefaultsToCollectionConfig = (collection: CollectionConfig): CollectionConfig => { | ||
| const access = collection.access | ||
|
|
||
| collection.access = { | ||
| create: defaultAccess, | ||
| delete: defaultAccess, | ||
| read: defaultAccess, | ||
| unlock: defaultAccess, | ||
| update: defaultAccess, | ||
| ...(collection.access || {}), | ||
| } | ||
| ...access, | ||
| create: access?.create ?? defaultAccess, | ||
| delete: access?.delete ?? defaultAccess, | ||
| read: access?.read ?? defaultAccess, | ||
| unlock: access?.unlock ?? defaultAccess, | ||
| update: access?.update ?? defaultAccess, | ||
| } satisfies SanitizedCollectionConfig['access'] | ||
|
|
||
| collection.admin = { | ||
| components: {}, | ||
|
|
@@ -84,26 +87,29 @@ export const addDefaultsToCollectionConfig = (collection: CollectionConfig): Col | |
| collection.fields = collection.fields ?? [] | ||
| collection.hierarchy = collection.hierarchy ?? false | ||
|
|
||
| const hooks = collection.hooks | ||
|
|
||
| collection.hooks = { | ||
| afterChange: [], | ||
| afterDelete: [], | ||
| afterForgotPassword: [], | ||
| afterLogin: [], | ||
| afterLogout: [], | ||
| afterMe: [], | ||
| afterOperation: [], | ||
| afterRead: [], | ||
| afterRefresh: [], | ||
| beforeChange: [], | ||
| beforeDelete: [], | ||
| beforeLogin: [], | ||
| beforeOperation: [], | ||
| beforeRead: [], | ||
| beforeValidate: [], | ||
| me: [], | ||
| refresh: [], | ||
| ...(collection.hooks || {}), | ||
| } | ||
| ...hooks, | ||
| afterChange: hooks?.afterChange ?? [], | ||
| afterDelete: hooks?.afterDelete ?? [], | ||
| afterError: hooks?.afterError ?? [], | ||
| afterForgotPassword: hooks?.afterForgotPassword ?? [], | ||
| afterLogin: hooks?.afterLogin ?? [], | ||
| afterLogout: hooks?.afterLogout ?? [], | ||
| afterMe: hooks?.afterMe ?? [], | ||
| afterOperation: hooks?.afterOperation ?? [], | ||
| afterRead: hooks?.afterRead ?? [], | ||
| afterRefresh: hooks?.afterRefresh ?? [], | ||
| beforeChange: hooks?.beforeChange ?? [], | ||
| beforeDelete: hooks?.beforeDelete ?? [], | ||
| beforeLogin: hooks?.beforeLogin ?? [], | ||
| beforeOperation: hooks?.beforeOperation ?? [], | ||
| beforeRead: hooks?.beforeRead ?? [], | ||
| beforeValidate: hooks?.beforeValidate ?? [], | ||
| me: hooks?.me ?? [], | ||
| refresh: hooks?.refresh ?? [], | ||
| } satisfies SanitizedCollectionConfig['hooks'] | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By using satisfies, we get proper errors if this does not match the sanitized types. If we had that before, we would have caught the type <=> runtime mismatch |
||
|
|
||
| collection.timestamps = collection.timestamps ?? true | ||
| collection.upload = collection.upload ?? false | ||
|
|
@@ -114,16 +120,20 @@ export const addDefaultsToCollectionConfig = (collection: CollectionConfig): Col | |
| return collection | ||
| } | ||
|
|
||
| export const addDefaultsToAuthConfig = (auth: IncomingAuthType): IncomingAuthType => { | ||
| export const addDefaultsToAuthConfig = (auth: IncomingAuthType): Auth => { | ||
| auth.cookies = { | ||
| sameSite: 'Lax', | ||
| secure: false, | ||
| ...(auth.cookies || {}), | ||
| } | ||
| sameSite: auth.cookies?.sameSite ?? 'Lax', | ||
| secure: auth.cookies?.secure ?? false, | ||
| } satisfies Auth['cookies'] | ||
|
|
||
| auth.forgotPassword = auth.forgotPassword ?? {} | ||
| auth.lockTime = auth.lockTime ?? 600000 // 10 minutes | ||
| auth.loginWithUsername = auth.loginWithUsername ?? false | ||
| auth.loginWithUsername = auth.loginWithUsername | ||
| ? addDefaultsToLoginWithUsernameConfig( | ||
| auth.loginWithUsername === true ? {} : auth.loginWithUsername, | ||
| ) | ||
| : false | ||
| auth.maxLoginAttempts = auth.maxLoginAttempts ?? 5 | ||
| auth.tokenExpiration = auth.tokenExpiration ?? 7200 | ||
| auth.useSessions = auth.useSessions ?? true | ||
|
|
@@ -134,24 +144,26 @@ export const addDefaultsToAuthConfig = (auth: IncomingAuthType): IncomingAuthTyp | |
| auth.verify = {} | ||
| } | ||
|
|
||
| return auth | ||
| return auth as Auth | ||
| } | ||
|
|
||
| /** | ||
| * @deprecated - remove in 4.0. This is error-prone, as mutating this object will affect any objects that use the defaults as a base. | ||
| */ | ||
| export const loginWithUsernameDefaults: LoginWithUsernameOptions = { | ||
| export const loginWithUsernameDefaults: Required<LoginWithUsernameOptions> = { | ||
| allowEmailLogin: false, | ||
| requireEmail: false, | ||
| requireUsername: true, | ||
| } | ||
|
|
||
| export const addDefaultsToLoginWithUsernameConfig = ( | ||
| loginWithUsername: LoginWithUsernameOptions, | ||
| ): LoginWithUsernameOptions => | ||
| ): Required<LoginWithUsernameOptions> => | ||
| ({ | ||
| allowEmailLogin: false, | ||
| requireEmail: false, | ||
| requireUsername: true, | ||
| ...(loginWithUsername || {}), | ||
| }) as LoginWithUsernameOptions | ||
| ...loginWithUsername, | ||
| allowEmailLogin: loginWithUsername.allowEmailLogin ?? false, | ||
| requireEmail: loginWithUsername.requireEmail ?? false, | ||
| requireUsername: loginWithUsername.allowEmailLogin | ||
| ? (loginWithUsername.requireUsername ?? true) | ||
| : true, | ||
| }) as Required<LoginWithUsernameOptions> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,11 +24,7 @@ import { traverseForLocalizedFields } from '../../utilities/traverseForLocalized | |
| import { baseVersionFields } from '../../versions/baseFields.js' | ||
| import { versionDefaults } from '../../versions/defaults.js' | ||
| import { defaultCollectionEndpoints } from '../endpoints/index.js' | ||
| import { | ||
| addDefaultsToAuthConfig, | ||
| addDefaultsToCollectionConfig, | ||
| addDefaultsToLoginWithUsernameConfig, | ||
| } from './defaults.js' | ||
| import { addDefaultsToAuthConfig, addDefaultsToCollectionConfig } from './defaults.js' | ||
| import { sanitizeCompoundIndexes } from './sanitizeCompoundIndexes.js' | ||
| import { validateUseAsTitle } from './useAsTitle.js' | ||
|
|
||
|
|
@@ -333,24 +329,6 @@ export const sanitizeCollection = async ( | |
| // disable duplicate for auth enabled collections by default | ||
| sanitized.disableDuplicate = sanitized.disableDuplicate ?? true | ||
|
|
||
| if (sanitized.auth.loginWithUsername) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved into defaults.ts |
||
| if (sanitized.auth.loginWithUsername === true) { | ||
| sanitized.auth.loginWithUsername = addDefaultsToLoginWithUsernameConfig({}) | ||
| } else { | ||
| const loginWithUsernameWithDefaults = addDefaultsToLoginWithUsernameConfig( | ||
| sanitized.auth.loginWithUsername, | ||
| ) | ||
|
|
||
| // if allowEmailLogin is false, requireUsername must be true | ||
| if (loginWithUsernameWithDefaults.allowEmailLogin === false) { | ||
| loginWithUsernameWithDefaults.requireUsername = true | ||
| } | ||
| sanitized.auth.loginWithUsername = loginWithUsernameWithDefaults | ||
| } | ||
| } else { | ||
| sanitized.auth.loginWithUsername = false | ||
| } | ||
|
|
||
| if (!collection?.admin?.useAsTitle) { | ||
| sanitized.admin!.useAsTitle = sanitized.auth.loginWithUsername ? 'username' : 'email' | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not all access fns are required, e.g. readVersions. So this is more accurate