payload 3.89.0 → 3.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/forms/Field.d.ts +2 -0
- package/dist/admin/forms/Field.d.ts.map +1 -1
- package/dist/admin/forms/Field.js.map +1 -1
- package/dist/admin/forms/Form.d.ts +2 -0
- package/dist/admin/forms/Form.d.ts.map +1 -1
- package/dist/admin/forms/Form.js.map +1 -1
- package/dist/admin/functions/index.d.ts +3 -1
- package/dist/admin/functions/index.d.ts.map +1 -1
- package/dist/admin/functions/index.js.map +1 -1
- package/dist/admin/views/dashboard.d.ts +1 -1
- package/dist/admin/views/dashboard.d.ts.map +1 -1
- package/dist/admin/views/dashboard.js.map +1 -1
- package/dist/auth/applyUserReadAccess.d.ts +15 -0
- package/dist/auth/applyUserReadAccess.d.ts.map +1 -0
- package/dist/auth/applyUserReadAccess.js +41 -0
- package/dist/auth/applyUserReadAccess.js.map +1 -0
- package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.js +20 -0
- package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.js.map +1 -0
- package/dist/auth/baseFields/apiKey/encryptAPIKey.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/encryptAPIKey.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/encryptAPIKey.js +16 -0
- package/dist/auth/baseFields/apiKey/encryptAPIKey.js.map +1 -0
- package/dist/auth/baseFields/apiKey/index.d.ts +6 -0
- package/dist/auth/baseFields/apiKey/index.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/index.js +112 -0
- package/dist/auth/baseFields/apiKey/index.js.map +1 -0
- package/dist/auth/baseFields/apiKey/omitAPIKey.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/omitAPIKey.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/omitAPIKey.js +8 -0
- package/dist/auth/baseFields/apiKey/omitAPIKey.js.map +1 -0
- package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.js +5 -0
- package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.js.map +1 -0
- package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.js +5 -0
- package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.js.map +1 -0
- package/dist/auth/baseFields/apiKey/setAPIKeyIndex.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/setAPIKeyIndex.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/setAPIKeyIndex.js +17 -0
- package/dist/auth/baseFields/apiKey/setAPIKeyIndex.js.map +1 -0
- package/dist/auth/baseFields/auth.d.ts +1 -0
- package/dist/auth/baseFields/auth.d.ts.map +1 -1
- package/dist/auth/baseFields/auth.js +9 -0
- package/dist/auth/baseFields/auth.js.map +1 -1
- package/dist/auth/defaultUnlockAccess.d.ts +3 -0
- package/dist/auth/defaultUnlockAccess.d.ts.map +1 -0
- package/dist/auth/defaultUnlockAccess.js +8 -0
- package/dist/auth/defaultUnlockAccess.js.map +1 -0
- package/dist/auth/endpoints/index.d.ts +1 -0
- package/dist/auth/endpoints/index.d.ts.map +1 -1
- package/dist/auth/endpoints/index.js +8 -0
- package/dist/auth/endpoints/index.js.map +1 -1
- package/dist/auth/endpoints/revealAPIKey.d.ts +7 -0
- package/dist/auth/endpoints/revealAPIKey.d.ts.map +1 -0
- package/dist/auth/endpoints/revealAPIKey.js +120 -0
- package/dist/auth/endpoints/revealAPIKey.js.map +1 -0
- package/dist/auth/executeAccess.d.ts +3 -1
- package/dist/auth/executeAccess.d.ts.map +1 -1
- package/dist/auth/executeAccess.js +2 -1
- package/dist/auth/executeAccess.js.map +1 -1
- package/dist/auth/getAuthFields.d.ts +1 -1
- package/dist/auth/getAuthFields.d.ts.map +1 -1
- package/dist/auth/getAuthFields.js +7 -4
- package/dist/auth/getAuthFields.js.map +1 -1
- package/dist/auth/getFieldsToSign.d.ts.map +1 -1
- package/dist/auth/getFieldsToSign.js +17 -10
- package/dist/auth/getFieldsToSign.js.map +1 -1
- package/dist/auth/getFieldsToSign.spec.js +114 -0
- package/dist/auth/getFieldsToSign.spec.js.map +1 -0
- package/dist/auth/jwt.d.ts.map +1 -1
- package/dist/auth/jwt.js +2 -0
- package/dist/auth/jwt.js.map +1 -1
- package/dist/auth/jwt.spec.js +24 -0
- package/dist/auth/jwt.spec.js.map +1 -0
- package/dist/auth/jwtAuth.d.ts +9 -0
- package/dist/auth/jwtAuth.d.ts.map +1 -0
- package/dist/auth/jwtAuth.js +8 -0
- package/dist/auth/jwtAuth.js.map +1 -0
- package/dist/auth/operations/auth.d.ts.map +1 -1
- package/dist/auth/operations/auth.js +2 -1
- package/dist/auth/operations/auth.js.map +1 -1
- package/dist/auth/operations/forgotPassword.d.ts.map +1 -1
- package/dist/auth/operations/forgotPassword.js +95 -7
- package/dist/auth/operations/forgotPassword.js.map +1 -1
- package/dist/auth/operations/local/auth.js +3 -1
- package/dist/auth/operations/local/auth.js.map +1 -1
- package/dist/auth/operations/login.d.ts.map +1 -1
- package/dist/auth/operations/login.js +48 -26
- package/dist/auth/operations/login.js.map +1 -1
- package/dist/auth/operations/refresh.d.ts.map +1 -1
- package/dist/auth/operations/refresh.js +32 -9
- package/dist/auth/operations/refresh.js.map +1 -1
- package/dist/auth/operations/registerFirstUser.d.ts.map +1 -1
- package/dist/auth/operations/registerFirstUser.js +3 -4
- package/dist/auth/operations/registerFirstUser.js.map +1 -1
- package/dist/auth/operations/resetPassword.d.ts.map +1 -1
- package/dist/auth/operations/resetPassword.js +31 -22
- package/dist/auth/operations/resetPassword.js.map +1 -1
- package/dist/auth/operations/unlock.d.ts.map +1 -1
- package/dist/auth/operations/unlock.js +1 -0
- package/dist/auth/operations/unlock.js.map +1 -1
- package/dist/auth/strategies/apiKey.d.ts.map +1 -1
- package/dist/auth/strategies/apiKey.js +15 -1
- package/dist/auth/strategies/apiKey.js.map +1 -1
- package/dist/auth/strategies/jwt.d.ts.map +1 -1
- package/dist/auth/strategies/jwt.js +61 -10
- package/dist/auth/strategies/jwt.js.map +1 -1
- package/dist/auth/strategies/jwt.spec.js +281 -0
- package/dist/auth/strategies/jwt.spec.js.map +1 -0
- package/dist/auth/strategies/local/authenticate.d.ts +5 -1
- package/dist/auth/strategies/local/authenticate.d.ts.map +1 -1
- package/dist/auth/strategies/local/authenticate.js +8 -3
- package/dist/auth/strategies/local/authenticate.js.map +1 -1
- package/dist/auth/strategies/local/authenticate.spec.js +37 -10
- package/dist/auth/strategies/local/authenticate.spec.js.map +1 -1
- package/dist/auth/strategies/local/generatePasswordSaltHash.d.ts +9 -1
- package/dist/auth/strategies/local/generatePasswordSaltHash.d.ts.map +1 -1
- package/dist/auth/strategies/local/generatePasswordSaltHash.js +61 -34
- package/dist/auth/strategies/local/generatePasswordSaltHash.js.map +1 -1
- package/dist/auth/types.d.ts +16 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/collections/config/client.d.ts +1 -0
- package/dist/collections/config/client.d.ts.map +1 -1
- package/dist/collections/config/client.js +3 -0
- package/dist/collections/config/client.js.map +1 -1
- package/dist/collections/config/defaults.d.ts.map +1 -1
- package/dist/collections/config/defaults.js +4 -2
- package/dist/collections/config/defaults.js.map +1 -1
- package/dist/collections/config/sanitize.d.ts.map +1 -1
- package/dist/collections/config/sanitize.js +17 -6
- package/dist/collections/config/sanitize.js.map +1 -1
- package/dist/collections/endpoints/index.d.ts +1 -0
- package/dist/collections/endpoints/index.d.ts.map +1 -1
- package/dist/collections/endpoints/index.js +6 -5
- package/dist/collections/endpoints/index.js.map +1 -1
- package/dist/collections/operations/create.d.ts.map +1 -1
- package/dist/collections/operations/create.js +105 -8
- package/dist/collections/operations/create.js.map +1 -1
- package/dist/collections/operations/deleteByID.js +13 -13
- package/dist/collections/operations/deleteByID.js.map +1 -1
- package/dist/collections/operations/findDistinct.d.ts.map +1 -1
- package/dist/collections/operations/findDistinct.js +96 -18
- package/dist/collections/operations/findDistinct.js.map +1 -1
- package/dist/collections/operations/restoreVersion.d.ts.map +1 -1
- package/dist/collections/operations/restoreVersion.js +47 -13
- package/dist/collections/operations/restoreVersion.js.map +1 -1
- package/dist/collections/operations/update.d.ts.map +1 -1
- package/dist/collections/operations/update.js +118 -18
- package/dist/collections/operations/update.js.map +1 -1
- package/dist/collections/operations/updateByID.d.ts.map +1 -1
- package/dist/collections/operations/updateByID.js +62 -2
- package/dist/collections/operations/updateByID.js.map +1 -1
- package/dist/collections/operations/utilities/update.d.ts.map +1 -1
- package/dist/collections/operations/utilities/update.js +71 -15
- package/dist/collections/operations/utilities/update.js.map +1 -1
- package/dist/config/orderable/index.d.ts.map +1 -1
- package/dist/config/orderable/index.js +67 -14
- package/dist/config/orderable/index.js.map +1 -1
- package/dist/config/sanitize.d.ts.map +1 -1
- package/dist/config/sanitize.js +1 -1
- package/dist/config/sanitize.js.map +1 -1
- package/dist/config/types.d.ts +13 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js.map +1 -1
- package/dist/database/getLocalizedPaths.d.ts +2 -1
- package/dist/database/getLocalizedPaths.d.ts.map +1 -1
- package/dist/database/getLocalizedPaths.js +12 -3
- package/dist/database/getLocalizedPaths.js.map +1 -1
- package/dist/database/isNestedRelationshipQuery.d.ts +9 -0
- package/dist/database/isNestedRelationshipQuery.d.ts.map +1 -0
- package/dist/database/isNestedRelationshipQuery.js +14 -0
- package/dist/database/isNestedRelationshipQuery.js.map +1 -0
- package/dist/database/prefixWherePaths.d.ts +6 -0
- package/dist/database/prefixWherePaths.d.ts.map +1 -0
- package/dist/database/prefixWherePaths.js +19 -0
- package/dist/database/prefixWherePaths.js.map +1 -0
- package/dist/database/prefixWherePaths.spec.js +46 -0
- package/dist/database/prefixWherePaths.spec.js.map +1 -0
- package/dist/database/queryValidation/validateQueryPaths.d.ts +2 -1
- package/dist/database/queryValidation/validateQueryPaths.d.ts.map +1 -1
- package/dist/database/queryValidation/validateQueryPaths.js +4 -1
- package/dist/database/queryValidation/validateQueryPaths.js.map +1 -1
- package/dist/database/queryValidation/validateSearchParams.d.ts +2 -1
- package/dist/database/queryValidation/validateSearchParams.d.ts.map +1 -1
- package/dist/database/queryValidation/validateSearchParams.js +82 -4
- package/dist/database/queryValidation/validateSearchParams.js.map +1 -1
- package/dist/database/types.d.ts +5 -0
- package/dist/database/types.d.ts.map +1 -1
- package/dist/database/types.js.map +1 -1
- package/dist/email/normalizeSendEmailOptions.js.map +1 -1
- package/dist/email/types.d.ts +4 -1
- package/dist/email/types.d.ts.map +1 -1
- package/dist/email/types.js.map +1 -1
- package/dist/exports/internal.d.ts +7 -0
- package/dist/exports/internal.d.ts.map +1 -1
- package/dist/exports/internal.js +7 -1
- package/dist/exports/internal.js.map +1 -1
- package/dist/exports/shared.d.ts +3 -0
- package/dist/exports/shared.d.ts.map +1 -1
- package/dist/exports/shared.js +3 -0
- package/dist/exports/shared.js.map +1 -1
- package/dist/fields/config/types.d.ts +7 -5
- package/dist/fields/config/types.d.ts.map +1 -1
- package/dist/fields/config/types.js.map +1 -1
- package/dist/fields/hooks/beforeChange/index.d.ts +7 -1
- package/dist/fields/hooks/beforeChange/index.d.ts.map +1 -1
- package/dist/fields/hooks/beforeChange/index.js +4 -2
- package/dist/fields/hooks/beforeChange/index.js.map +1 -1
- package/dist/fields/hooks/beforeChange/index.spec.js +206 -0
- package/dist/fields/hooks/beforeChange/index.spec.js.map +1 -0
- package/dist/fields/hooks/beforeChange/promise.d.ts +2 -1
- package/dist/fields/hooks/beforeChange/promise.d.ts.map +1 -1
- package/dist/fields/hooks/beforeChange/promise.js +51 -9
- package/dist/fields/hooks/beforeChange/promise.js.map +1 -1
- package/dist/fields/hooks/beforeChange/traverseFields.d.ts +2 -1
- package/dist/fields/hooks/beforeChange/traverseFields.d.ts.map +1 -1
- package/dist/fields/hooks/beforeChange/traverseFields.js +3 -2
- package/dist/fields/hooks/beforeChange/traverseFields.js.map +1 -1
- package/dist/fields/hooks/beforeDuplicate/promise.d.ts.map +1 -1
- package/dist/fields/hooks/beforeDuplicate/promise.js +4 -0
- package/dist/fields/hooks/beforeDuplicate/promise.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/getFallbackValue.d.ts +2 -1
- package/dist/fields/hooks/beforeValidate/getFallbackValue.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/getFallbackValue.js +2 -2
- package/dist/fields/hooks/beforeValidate/getFallbackValue.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/index.d.ts +6 -1
- package/dist/fields/hooks/beforeValidate/index.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/index.js +3 -1
- package/dist/fields/hooks/beforeValidate/index.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/index.spec.js +90 -0
- package/dist/fields/hooks/beforeValidate/index.spec.js.map +1 -0
- package/dist/fields/hooks/beforeValidate/promise.d.ts +6 -1
- package/dist/fields/hooks/beforeValidate/promise.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/promise.js +23 -8
- package/dist/fields/hooks/beforeValidate/promise.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/traverseFields.d.ts +6 -1
- package/dist/fields/hooks/beforeValidate/traverseFields.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/traverseFields.js +3 -1
- package/dist/fields/hooks/beforeValidate/traverseFields.js.map +1 -1
- package/dist/fields/validations.d.ts.map +1 -1
- package/dist/fields/validations.js +3 -1
- package/dist/fields/validations.js.map +1 -1
- package/dist/globals/operations/restoreVersion.d.ts.map +1 -1
- package/dist/globals/operations/restoreVersion.js +45 -11
- package/dist/globals/operations/restoreVersion.js.map +1 -1
- package/dist/globals/operations/update.d.ts.map +1 -1
- package/dist/globals/operations/update.js +95 -16
- package/dist/globals/operations/update.js.map +1 -1
- package/dist/index.bundled.d.ts +102 -24
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/preferences/config.d.ts.map +1 -1
- package/dist/preferences/config.js +2 -1
- package/dist/preferences/config.js.map +1 -1
- package/dist/query-presets/access.d.ts.map +1 -1
- package/dist/query-presets/access.js +2 -1
- package/dist/query-presets/access.js.map +1 -1
- package/dist/queues/config/types/index.d.ts +2 -1
- package/dist/queues/config/types/index.d.ts.map +1 -1
- package/dist/queues/config/types/index.js.map +1 -1
- package/dist/uploads/assertClientUploadAccess.d.ts +7 -0
- package/dist/uploads/assertClientUploadAccess.d.ts.map +1 -0
- package/dist/uploads/assertClientUploadAccess.js +27 -0
- package/dist/uploads/assertClientUploadAccess.js.map +1 -0
- package/dist/uploads/assertClientUploadAllowed.d.ts +11 -0
- package/dist/uploads/assertClientUploadAllowed.d.ts.map +1 -0
- package/dist/uploads/assertClientUploadAllowed.js +46 -0
- package/dist/uploads/assertClientUploadAllowed.js.map +1 -0
- package/dist/uploads/assertClientUploadAllowed.spec.js +91 -0
- package/dist/uploads/assertClientUploadAllowed.spec.js.map +1 -0
- package/dist/uploads/checkFileRestrictions.d.ts +4 -1
- package/dist/uploads/checkFileRestrictions.d.ts.map +1 -1
- package/dist/uploads/checkFileRestrictions.js +66 -26
- package/dist/uploads/checkFileRestrictions.js.map +1 -1
- package/dist/uploads/clientUploadReceipt.d.ts +23 -0
- package/dist/uploads/clientUploadReceipt.d.ts.map +1 -0
- package/dist/uploads/clientUploadReceipt.js +55 -0
- package/dist/uploads/clientUploadReceipt.js.map +1 -0
- package/dist/uploads/deleteAssociatedFiles.d.ts.map +1 -1
- package/dist/uploads/deleteAssociatedFiles.js +33 -8
- package/dist/uploads/deleteAssociatedFiles.js.map +1 -1
- package/dist/uploads/deleteAssociatedFiles.spec.js +124 -0
- package/dist/uploads/deleteAssociatedFiles.spec.js.map +1 -0
- package/dist/uploads/detectSvgFromXml.d.ts +1 -3
- package/dist/uploads/detectSvgFromXml.d.ts.map +1 -1
- package/dist/uploads/detectSvgFromXml.js +3 -40
- package/dist/uploads/detectSvgFromXml.js.map +1 -1
- package/dist/uploads/endpoints/getFile.d.ts.map +1 -1
- package/dist/uploads/endpoints/getFile.js +5 -3
- package/dist/uploads/endpoints/getFile.js.map +1 -1
- package/dist/uploads/fetchAPI-multipart/handlers.d.ts +2 -2
- package/dist/uploads/fetchAPI-multipart/handlers.d.ts.map +1 -1
- package/dist/uploads/fetchAPI-multipart/handlers.js +18 -5
- package/dist/uploads/fetchAPI-multipart/handlers.js.map +1 -1
- package/dist/uploads/fetchAPI-multipart/index.d.ts.map +1 -1
- package/dist/uploads/fetchAPI-multipart/index.js +24 -2
- package/dist/uploads/fetchAPI-multipart/index.js.map +1 -1
- package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts +0 -6
- package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts.map +1 -1
- package/dist/uploads/fetchAPI-multipart/processMultipart.js +146 -126
- package/dist/uploads/fetchAPI-multipart/processMultipart.js.map +1 -1
- package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js +663 -0
- package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js.map +1 -0
- package/dist/uploads/generateFileData.d.ts +3 -1
- package/dist/uploads/generateFileData.d.ts.map +1 -1
- package/dist/uploads/generateFileData.js +29 -16
- package/dist/uploads/generateFileData.js.map +1 -1
- package/dist/uploads/getBaseFields.d.ts.map +1 -1
- package/dist/uploads/getBaseFields.js +3 -1
- package/dist/uploads/getBaseFields.js.map +1 -1
- package/dist/uploads/getExternalFile.d.ts +1 -1
- package/dist/uploads/getExternalFile.d.ts.map +1 -1
- package/dist/uploads/getExternalFile.js +60 -15
- package/dist/uploads/getExternalFile.js.map +1 -1
- package/dist/uploads/getFileTypeFallback.d.ts.map +1 -1
- package/dist/uploads/getFileTypeFallback.js +3 -1
- package/dist/uploads/getFileTypeFallback.js.map +1 -1
- package/dist/uploads/getFileTypeIdentity.d.ts +19 -0
- package/dist/uploads/getFileTypeIdentity.d.ts.map +1 -0
- package/dist/uploads/getFileTypeIdentity.js +34 -0
- package/dist/uploads/getFileTypeIdentity.js.map +1 -0
- package/dist/uploads/getFileTypeIdentity.spec.js +43 -0
- package/dist/uploads/getFileTypeIdentity.spec.js.map +1 -0
- package/dist/uploads/isProcessableImage.d.ts +2 -0
- package/dist/uploads/isProcessableImage.d.ts.map +1 -0
- package/dist/uploads/isProcessableImage.js +7 -0
- package/dist/uploads/isProcessableImage.js.map +1 -0
- package/dist/uploads/safeFetch.d.ts.map +1 -1
- package/dist/uploads/safeFetch.js +3 -2
- package/dist/uploads/safeFetch.js.map +1 -1
- package/dist/uploads/sanitizeUploadData.d.ts +26 -0
- package/dist/uploads/sanitizeUploadData.d.ts.map +1 -0
- package/dist/uploads/sanitizeUploadData.js +159 -0
- package/dist/uploads/sanitizeUploadData.js.map +1 -0
- package/dist/uploads/sanitizeUploadData.spec.js +255 -0
- package/dist/uploads/sanitizeUploadData.spec.js.map +1 -0
- package/dist/uploads/types.d.ts +11 -3
- package/dist/uploads/types.d.ts.map +1 -1
- package/dist/uploads/types.js.map +1 -1
- package/dist/uploads/uploadContentSecurityPolicy.d.ts +2 -0
- package/dist/uploads/uploadContentSecurityPolicy.d.ts.map +1 -0
- package/dist/uploads/uploadContentSecurityPolicy.js +3 -0
- package/dist/uploads/uploadContentSecurityPolicy.js.map +1 -0
- package/dist/uploads/validateISOBaseMediaFile.d.ts +4 -0
- package/dist/uploads/validateISOBaseMediaFile.d.ts.map +1 -0
- package/dist/uploads/validateISOBaseMediaFile.js +91 -0
- package/dist/uploads/validateISOBaseMediaFile.js.map +1 -0
- package/dist/uploads/validateISOBaseMediaFile.spec.js +130 -0
- package/dist/uploads/validateISOBaseMediaFile.spec.js.map +1 -0
- package/dist/uploads/validateSvg.d.ts +10 -4
- package/dist/uploads/validateSvg.d.ts.map +1 -1
- package/dist/uploads/validateSvg.js +280 -40
- package/dist/uploads/validateSvg.js.map +1 -1
- package/dist/uploads/validateSvg.spec.js +123 -0
- package/dist/uploads/validateSvg.spec.js.map +1 -0
- package/dist/uploads/validateUploadFilename.d.ts +3 -0
- package/dist/uploads/validateUploadFilename.d.ts.map +1 -0
- package/dist/uploads/validateUploadFilename.js +13 -0
- package/dist/uploads/validateUploadFilename.js.map +1 -0
- package/dist/uploads/validateUploadFilename.spec.js +24 -0
- package/dist/uploads/validateUploadFilename.spec.js.map +1 -0
- package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
- package/dist/utilities/addDataAndFileToRequest.js +28 -1
- package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
- package/dist/utilities/addDataAndFileToRequest.spec.js +158 -0
- package/dist/utilities/addDataAndFileToRequest.spec.js.map +1 -1
- package/dist/utilities/createPayloadRequest.d.ts.map +1 -1
- package/dist/utilities/createPayloadRequest.js +2 -1
- package/dist/utilities/createPayloadRequest.js.map +1 -1
- package/dist/utilities/deepCopyObject.d.ts.map +1 -1
- package/dist/utilities/deepCopyObject.js +110 -19
- package/dist/utilities/deepCopyObject.js.map +1 -1
- package/dist/utilities/deepCopyObject.spec.js +216 -0
- package/dist/utilities/deepCopyObject.spec.js.map +1 -0
- package/dist/utilities/fieldPath.d.ts +13 -0
- package/dist/utilities/fieldPath.d.ts.map +1 -0
- package/dist/utilities/fieldPath.js +51 -0
- package/dist/utilities/fieldPath.js.map +1 -0
- package/dist/utilities/fieldPath.spec.js +155 -0
- package/dist/utilities/fieldPath.spec.js.map +1 -0
- package/dist/utilities/getEntityPermissions/getEntityPermissions.d.ts.map +1 -1
- package/dist/utilities/getEntityPermissions/getEntityPermissions.js +2 -0
- package/dist/utilities/getEntityPermissions/getEntityPermissions.js.map +1 -1
- package/dist/utilities/getObjectDotNotation.d.ts.map +1 -1
- package/dist/utilities/getObjectDotNotation.js +7 -1
- package/dist/utilities/getObjectDotNotation.js.map +1 -1
- package/dist/utilities/getObjectDotNotation.spec.js +33 -0
- package/dist/utilities/getObjectDotNotation.spec.js.map +1 -0
- package/dist/utilities/getSafeRedirect.spec.js +14 -0
- package/dist/utilities/getSafeRedirect.spec.js.map +1 -1
- package/dist/utilities/getTopLevelFieldNames.d.ts +2 -0
- package/dist/utilities/getTopLevelFieldNames.d.ts.map +1 -0
- package/dist/utilities/getTopLevelFieldNames.js +3 -0
- package/dist/utilities/getTopLevelFieldNames.js.map +1 -0
- package/dist/utilities/getTopLevelFieldNames.spec.js +18 -0
- package/dist/utilities/getTopLevelFieldNames.spec.js.map +1 -0
- package/dist/utilities/handleEndpoints.js +1 -1
- package/dist/utilities/handleEndpoints.js.map +1 -1
- package/dist/utilities/parseParams/index.d.ts.map +1 -1
- package/dist/utilities/parseParams/index.js +3 -1
- package/dist/utilities/parseParams/index.js.map +1 -1
- package/dist/utilities/parseParams/index.spec.js +8 -0
- package/dist/utilities/parseParams/index.spec.js.map +1 -1
- package/dist/utilities/traverseFields.d.ts.map +1 -1
- package/dist/utilities/traverseFields.js +5 -14
- package/dist/utilities/traverseFields.js.map +1 -1
- package/dist/utilities/unflatten.d.ts.map +1 -1
- package/dist/utilities/unflatten.js +21 -9
- package/dist/utilities/unflatten.js.map +1 -1
- package/dist/utilities/unflatten.spec.js +38 -0
- package/dist/utilities/unflatten.spec.js.map +1 -0
- package/dist/versions/allLocalesPublicationStatus.d.ts +38 -0
- package/dist/versions/allLocalesPublicationStatus.d.ts.map +1 -0
- package/dist/versions/allLocalesPublicationStatus.js +57 -0
- package/dist/versions/allLocalesPublicationStatus.js.map +1 -0
- package/dist/versions/getLatestGlobalVersion.d.ts.map +1 -1
- package/dist/versions/getLatestGlobalVersion.js +10 -2
- package/dist/versions/getLatestGlobalVersion.js.map +1 -1
- package/dist/versions/getRestoredStatusesToAuthorize.d.ts +9 -0
- package/dist/versions/getRestoredStatusesToAuthorize.d.ts.map +1 -0
- package/dist/versions/getRestoredStatusesToAuthorize.js +22 -0
- package/dist/versions/getRestoredStatusesToAuthorize.js.map +1 -0
- package/dist/versions/getRestoredStatusesToAuthorize.spec.js +50 -0
- package/dist/versions/getRestoredStatusesToAuthorize.spec.js.map +1 -0
- package/dist/versions/schedule/job.d.ts +2 -2
- package/dist/versions/schedule/job.d.ts.map +1 -1
- package/dist/versions/schedule/job.js +11 -7
- package/dist/versions/schedule/job.js.map +1 -1
- package/dist/versions/schedule/types.d.ts +5 -1
- package/dist/versions/schedule/types.d.ts.map +1 -1
- package/dist/versions/schedule/types.js.map +1 -1
- package/package.json +4 -2
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { jwtVerify } from 'jose';
|
|
2
2
|
import { extractJWT } from '../extractJWT.js';
|
|
3
|
-
|
|
3
|
+
import { JWT_AUTH_VERSION } from '../jwtAuth.js';
|
|
4
|
+
const getJWTAuthenticationReference = (token)=>{
|
|
5
|
+
const { id, collection, sid } = token;
|
|
6
|
+
if (typeof collection !== 'string' || typeof id !== 'number' && typeof id !== 'string' || typeof sid !== 'undefined' && typeof sid !== 'string') {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
id,
|
|
11
|
+
collection,
|
|
12
|
+
...sid ? {
|
|
13
|
+
sid
|
|
14
|
+
} : {}
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
async function autoLogin({ fallbackLocale, isGraphQL, locale, payload, req, strategyName = 'local-jwt' }) {
|
|
4
18
|
if (typeof payload?.config?.admin?.autoLogin !== 'object' || payload.config.admin?.autoLogin.prefillOnly || !payload?.config?.admin?.autoLogin || !payload.config.admin?.autoLogin.email && !payload.config.admin?.autoLogin.username) {
|
|
5
19
|
return {
|
|
6
20
|
user: null
|
|
@@ -26,8 +40,11 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
|
|
|
26
40
|
const user = (await payload.find({
|
|
27
41
|
collection: collection.config.slug,
|
|
28
42
|
depth: isGraphQL ? 0 : collection.config.auth.depth,
|
|
43
|
+
fallbackLocale,
|
|
29
44
|
limit: 1,
|
|
45
|
+
locale,
|
|
30
46
|
pagination: false,
|
|
47
|
+
req,
|
|
31
48
|
where
|
|
32
49
|
})).docs[0];
|
|
33
50
|
if (!user) {
|
|
@@ -43,7 +60,10 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
|
|
|
43
60
|
}
|
|
44
61
|
/**
|
|
45
62
|
* Authentication strategy function for JWT tokens
|
|
46
|
-
*/ export const JWTAuthentication = async ({ headers, isGraphQL = false, payload, strategyName = 'local-jwt' })=>{
|
|
63
|
+
*/ export const JWTAuthentication = async ({ headers, isGraphQL = false, payload, req, strategyName = 'local-jwt' })=>{
|
|
64
|
+
const requestFallbackLocale = req?.fallbackLocale;
|
|
65
|
+
const requestLocale = req?.locale;
|
|
66
|
+
const requestDepth = req?.query?.depth;
|
|
47
67
|
try {
|
|
48
68
|
const token = extractJWT({
|
|
49
69
|
headers,
|
|
@@ -52,8 +72,11 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
|
|
|
52
72
|
if (!token) {
|
|
53
73
|
if (headers.get('DisableAutologin') !== 'true') {
|
|
54
74
|
return await autoLogin({
|
|
75
|
+
fallbackLocale: requestFallbackLocale,
|
|
55
76
|
isGraphQL,
|
|
77
|
+
locale: requestLocale,
|
|
56
78
|
payload,
|
|
79
|
+
req,
|
|
57
80
|
strategyName
|
|
58
81
|
});
|
|
59
82
|
}
|
|
@@ -62,22 +85,36 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
|
|
|
62
85
|
};
|
|
63
86
|
}
|
|
64
87
|
const secretKey = new TextEncoder().encode(payload.secret);
|
|
65
|
-
const { payload: decodedPayload } = await jwtVerify(token, secretKey);
|
|
66
|
-
|
|
88
|
+
const { payload: decodedPayload, protectedHeader } = await jwtVerify(token, secretKey);
|
|
89
|
+
if (protectedHeader.authVersion !== JWT_AUTH_VERSION) {
|
|
90
|
+
throw new Error('Invalid JWT protected header');
|
|
91
|
+
}
|
|
92
|
+
const authenticationReference = getJWTAuthenticationReference(decodedPayload);
|
|
93
|
+
if (!authenticationReference) {
|
|
94
|
+
throw new Error('Invalid JWT authentication reference');
|
|
95
|
+
}
|
|
96
|
+
const collection = Object.prototype.hasOwnProperty.call(payload.collections, authenticationReference.collection) ? payload.collections[authenticationReference.collection] : undefined;
|
|
97
|
+
if (!collection?.config.auth || collection.config.auth.disableLocalStrategy) {
|
|
98
|
+
throw new Error('JWT authentication requires an auth-enabled local collection');
|
|
99
|
+
}
|
|
67
100
|
const user = await payload.findByID({
|
|
68
|
-
id:
|
|
69
|
-
collection:
|
|
70
|
-
depth: isGraphQL ? 0 : collection.config.auth.depth
|
|
101
|
+
id: authenticationReference.id,
|
|
102
|
+
collection: authenticationReference.collection,
|
|
103
|
+
depth: isGraphQL ? 0 : collection.config.auth.depth,
|
|
104
|
+
fallbackLocale: requestFallbackLocale,
|
|
105
|
+
locale: requestLocale,
|
|
106
|
+
overrideAccess: true,
|
|
107
|
+
req
|
|
71
108
|
});
|
|
72
109
|
if (user && (!collection.config.auth.verify || user._verified)) {
|
|
73
110
|
if (collection.config.auth.useSessions) {
|
|
74
|
-
const existingSession = (user.sessions || []).find(({ id })=>id ===
|
|
75
|
-
if (!existingSession || !
|
|
111
|
+
const existingSession = (user.sessions || []).find(({ id })=>id === authenticationReference.sid);
|
|
112
|
+
if (!existingSession || !authenticationReference.sid) {
|
|
76
113
|
return {
|
|
77
114
|
user: null
|
|
78
115
|
};
|
|
79
116
|
}
|
|
80
|
-
user._sid =
|
|
117
|
+
user._sid = authenticationReference.sid;
|
|
81
118
|
}
|
|
82
119
|
user.collection = collection.config.slug;
|
|
83
120
|
user._strategy = strategyName;
|
|
@@ -87,8 +124,11 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
|
|
|
87
124
|
} else {
|
|
88
125
|
if (headers.get('DisableAutologin') !== 'true') {
|
|
89
126
|
return await autoLogin({
|
|
127
|
+
fallbackLocale: requestFallbackLocale,
|
|
90
128
|
isGraphQL,
|
|
129
|
+
locale: requestLocale,
|
|
91
130
|
payload,
|
|
131
|
+
req,
|
|
92
132
|
strategyName
|
|
93
133
|
});
|
|
94
134
|
}
|
|
@@ -99,14 +139,25 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
|
|
|
99
139
|
} catch (ignore) {
|
|
100
140
|
if (headers.get('DisableAutologin') !== 'true') {
|
|
101
141
|
return await autoLogin({
|
|
142
|
+
fallbackLocale: requestFallbackLocale,
|
|
102
143
|
isGraphQL,
|
|
144
|
+
locale: requestLocale,
|
|
103
145
|
payload,
|
|
146
|
+
req,
|
|
104
147
|
strategyName
|
|
105
148
|
});
|
|
106
149
|
}
|
|
107
150
|
return {
|
|
108
151
|
user: null
|
|
109
152
|
};
|
|
153
|
+
} finally{
|
|
154
|
+
if (req) {
|
|
155
|
+
req.fallbackLocale = requestFallbackLocale;
|
|
156
|
+
req.locale = requestLocale;
|
|
157
|
+
if (req.query) {
|
|
158
|
+
req.query.depth = requestDepth;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
110
161
|
}
|
|
111
162
|
};
|
|
112
163
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/auth/strategies/jwt.ts"],"sourcesContent":["import { jwtVerify } from 'jose'\n\nimport type { Payload, Where } from '../../types/index.js'\nimport type { AuthStrategyFunction, AuthStrategyResult } from '../index.js'\n\nimport { extractJWT } from '../extractJWT.js'\n\ntype JWTToken = {\n collection: string\n id: string\n sid?: string\n}\n\nasync function autoLogin({\n isGraphQL,\n payload,\n strategyName = 'local-jwt',\n}: {\n isGraphQL: boolean\n payload: Payload\n strategyName?: string\n}): Promise<{\n user: AuthStrategyResult['user']\n}> {\n if (\n typeof payload?.config?.admin?.autoLogin !== 'object' ||\n payload.config.admin?.autoLogin.prefillOnly ||\n !payload?.config?.admin?.autoLogin ||\n (!payload.config.admin?.autoLogin.email && !payload.config.admin?.autoLogin.username)\n ) {\n return { user: null }\n }\n\n const collection = payload.collections[payload.config.admin.user]\n\n const where: Where = {\n or: [],\n }\n if (payload.config.admin?.autoLogin.email) {\n where.or?.push({\n email: {\n equals: payload.config.admin?.autoLogin.email,\n },\n })\n } else if (payload.config.admin?.autoLogin.username) {\n where.or?.push({\n username: {\n equals: payload.config.admin?.autoLogin.username,\n },\n })\n }\n\n const user = (\n await payload.find({\n collection: collection!.config.slug,\n depth: isGraphQL ? 0 : collection!.config.auth.depth,\n limit: 1,\n pagination: false,\n where,\n })\n ).docs[0] as AuthStrategyResult['user']\n\n if (!user) {\n return { user: null }\n }\n user.collection = collection!.config.slug\n user._strategy = strategyName\n\n return {\n user,\n }\n}\n\n/**\n * Authentication strategy function for JWT tokens\n */\nexport const JWTAuthentication: AuthStrategyFunction = async ({\n headers,\n isGraphQL = false,\n payload,\n strategyName = 'local-jwt',\n}) => {\n try {\n const token = extractJWT({ headers, payload })\n\n if (!token) {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({ isGraphQL, payload, strategyName })\n }\n return { user: null }\n }\n\n const secretKey = new TextEncoder().encode(payload.secret)\n const { payload: decodedPayload } = await jwtVerify<JWTToken>(token, secretKey)\n const collection = payload.collections[decodedPayload.collection]\n\n const user = (await payload.findByID({\n id: decodedPayload.id,\n collection: decodedPayload.collection,\n depth: isGraphQL ? 0 : collection!.config.auth.depth,\n })) as AuthStrategyResult['user']\n\n if (user && (!collection!.config.auth.verify || user._verified)) {\n if (collection!.config.auth.useSessions) {\n const existingSession = (user.sessions || []).find(({ id }) => id === decodedPayload.sid)\n\n if (!existingSession || !decodedPayload.sid) {\n return {\n user: null,\n }\n }\n\n user._sid = decodedPayload.sid\n }\n\n user.collection = collection!.config.slug\n user._strategy = strategyName\n return {\n user,\n }\n } else {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({ isGraphQL, payload, strategyName })\n }\n return { user: null }\n }\n } catch (ignore) {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({ isGraphQL, payload, strategyName })\n }\n return { user: null }\n }\n}\n"],"names":["jwtVerify","extractJWT","autoLogin","isGraphQL","payload","strategyName","config","admin","prefillOnly","email","username","user","collection","collections","where","or","push","equals","find","slug","depth","auth","limit","pagination","docs","_strategy","JWTAuthentication","headers","token","get","secretKey","TextEncoder","encode","secret","decodedPayload","findByID","id","verify","_verified","useSessions","existingSession","sessions","sid","_sid","ignore"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/auth/strategies/jwt.ts"],"sourcesContent":["import { jwtVerify } from 'jose'\n\nimport type { Payload, PayloadRequest, Where } from '../../types/index.js'\nimport type { AuthStrategyFunction, AuthStrategyResult } from '../index.js'\n\nimport { extractJWT } from '../extractJWT.js'\nimport { JWT_AUTH_VERSION } from '../jwtAuth.js'\n\ntype DecodedJWTPayload = Record<string, unknown>\n\ntype JWTAuthenticationReference = {\n collection: string\n id: number | string\n sid?: string\n}\n\nconst getJWTAuthenticationReference = (\n token: DecodedJWTPayload,\n): JWTAuthenticationReference | null => {\n const { id, collection, sid } = token\n\n if (\n typeof collection !== 'string' ||\n (typeof id !== 'number' && typeof id !== 'string') ||\n (typeof sid !== 'undefined' && typeof sid !== 'string')\n ) {\n return null\n }\n\n return {\n id,\n collection,\n ...(sid ? { sid } : {}),\n }\n}\n\nasync function autoLogin({\n fallbackLocale,\n isGraphQL,\n locale,\n payload,\n req,\n strategyName = 'local-jwt',\n}: {\n fallbackLocale?: PayloadRequest['fallbackLocale']\n isGraphQL: boolean\n locale?: PayloadRequest['locale']\n payload: Payload\n req?: PayloadRequest\n strategyName?: string\n}): Promise<{\n user: AuthStrategyResult['user']\n}> {\n if (\n typeof payload?.config?.admin?.autoLogin !== 'object' ||\n payload.config.admin?.autoLogin.prefillOnly ||\n !payload?.config?.admin?.autoLogin ||\n (!payload.config.admin?.autoLogin.email && !payload.config.admin?.autoLogin.username)\n ) {\n return { user: null }\n }\n\n const collection = payload.collections[payload.config.admin.user]\n\n const where: Where = {\n or: [],\n }\n if (payload.config.admin?.autoLogin.email) {\n where.or?.push({\n email: {\n equals: payload.config.admin?.autoLogin.email,\n },\n })\n } else if (payload.config.admin?.autoLogin.username) {\n where.or?.push({\n username: {\n equals: payload.config.admin?.autoLogin.username,\n },\n })\n }\n\n const user = (\n await payload.find({\n collection: collection!.config.slug,\n depth: isGraphQL ? 0 : collection!.config.auth.depth,\n fallbackLocale,\n limit: 1,\n locale,\n pagination: false,\n req,\n where,\n })\n ).docs[0] as AuthStrategyResult['user']\n\n if (!user) {\n return { user: null }\n }\n user.collection = collection!.config.slug\n user._strategy = strategyName\n\n return {\n user,\n }\n}\n\n/**\n * Authentication strategy function for JWT tokens\n */\nexport const JWTAuthentication: AuthStrategyFunction = async ({\n headers,\n isGraphQL = false,\n payload,\n req,\n strategyName = 'local-jwt',\n}) => {\n const requestFallbackLocale = req?.fallbackLocale\n const requestLocale = req?.locale\n const requestDepth = req?.query?.depth\n\n try {\n const token = extractJWT({ headers, payload })\n\n if (!token) {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({\n fallbackLocale: requestFallbackLocale,\n isGraphQL,\n locale: requestLocale,\n payload,\n req,\n strategyName,\n })\n }\n return { user: null }\n }\n\n const secretKey = new TextEncoder().encode(payload.secret)\n const { payload: decodedPayload, protectedHeader } = await jwtVerify<DecodedJWTPayload>(\n token,\n secretKey,\n )\n\n if (protectedHeader.authVersion !== JWT_AUTH_VERSION) {\n throw new Error('Invalid JWT protected header')\n }\n\n const authenticationReference = getJWTAuthenticationReference(decodedPayload)\n\n if (!authenticationReference) {\n throw new Error('Invalid JWT authentication reference')\n }\n\n const collection = Object.prototype.hasOwnProperty.call(\n payload.collections,\n authenticationReference.collection,\n )\n ? payload.collections[authenticationReference.collection]\n : undefined\n\n if (!collection?.config.auth || collection.config.auth.disableLocalStrategy) {\n throw new Error('JWT authentication requires an auth-enabled local collection')\n }\n\n const user = (await payload.findByID({\n id: authenticationReference.id,\n collection: authenticationReference.collection,\n depth: isGraphQL ? 0 : collection.config.auth.depth,\n fallbackLocale: requestFallbackLocale,\n locale: requestLocale,\n overrideAccess: true,\n req,\n })) as AuthStrategyResult['user']\n\n if (user && (!collection.config.auth.verify || user._verified)) {\n if (collection.config.auth.useSessions) {\n const existingSession = (user.sessions || []).find(\n ({ id }) => id === authenticationReference.sid,\n )\n\n if (!existingSession || !authenticationReference.sid) {\n return {\n user: null,\n }\n }\n\n user._sid = authenticationReference.sid\n }\n\n user.collection = collection.config.slug\n user._strategy = strategyName\n return {\n user,\n }\n } else {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({\n fallbackLocale: requestFallbackLocale,\n isGraphQL,\n locale: requestLocale,\n payload,\n req,\n strategyName,\n })\n }\n return { user: null }\n }\n } catch (ignore) {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({\n fallbackLocale: requestFallbackLocale,\n isGraphQL,\n locale: requestLocale,\n payload,\n req,\n strategyName,\n })\n }\n return { user: null }\n } finally {\n if (req) {\n req.fallbackLocale = requestFallbackLocale!\n req.locale = requestLocale!\n\n if (req.query) {\n req.query.depth = requestDepth\n }\n }\n }\n}\n"],"names":["jwtVerify","extractJWT","JWT_AUTH_VERSION","getJWTAuthenticationReference","token","id","collection","sid","autoLogin","fallbackLocale","isGraphQL","locale","payload","req","strategyName","config","admin","prefillOnly","email","username","user","collections","where","or","push","equals","find","slug","depth","auth","limit","pagination","docs","_strategy","JWTAuthentication","headers","requestFallbackLocale","requestLocale","requestDepth","query","get","secretKey","TextEncoder","encode","secret","decodedPayload","protectedHeader","authVersion","Error","authenticationReference","Object","prototype","hasOwnProperty","call","undefined","disableLocalStrategy","findByID","overrideAccess","verify","_verified","useSessions","existingSession","sessions","_sid","ignore"],"mappings":"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"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { SignJWT } from 'jose';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { getFieldsToSign } from '../getFieldsToSign.js';
|
|
4
|
+
import { jwtSign } from '../jwt.js';
|
|
5
|
+
import { JWT_AUTH_VERSION } from '../jwtAuth.js';
|
|
6
|
+
import { JWTAuthentication } from './jwt.js';
|
|
7
|
+
const secret = '01234567890123456789012345678901';
|
|
8
|
+
const signToken = async (tokenData, protectedHeader = {
|
|
9
|
+
authVersion: JWT_AUTH_VERSION
|
|
10
|
+
})=>new SignJWT(tokenData).setProtectedHeader({
|
|
11
|
+
alg: 'HS256',
|
|
12
|
+
typ: 'JWT',
|
|
13
|
+
...protectedHeader
|
|
14
|
+
}).sign(new TextEncoder().encode(secret));
|
|
15
|
+
const createPayload = ()=>({
|
|
16
|
+
collections: {
|
|
17
|
+
admins: {
|
|
18
|
+
config: {
|
|
19
|
+
auth: {
|
|
20
|
+
depth: 0,
|
|
21
|
+
useSessions: false,
|
|
22
|
+
verify: false
|
|
23
|
+
},
|
|
24
|
+
slug: 'admins'
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
disabledUsers: {
|
|
28
|
+
config: {
|
|
29
|
+
auth: {
|
|
30
|
+
depth: 0,
|
|
31
|
+
disableLocalStrategy: true,
|
|
32
|
+
useSessions: false,
|
|
33
|
+
verify: false
|
|
34
|
+
},
|
|
35
|
+
slug: 'disabledUsers'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
posts: {
|
|
39
|
+
config: {
|
|
40
|
+
auth: false,
|
|
41
|
+
slug: 'posts'
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
users: {
|
|
45
|
+
config: {
|
|
46
|
+
auth: {
|
|
47
|
+
depth: 0,
|
|
48
|
+
useSessions: false,
|
|
49
|
+
verify: false
|
|
50
|
+
},
|
|
51
|
+
slug: 'users'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
config: {
|
|
56
|
+
admin: {
|
|
57
|
+
autoLogin: {
|
|
58
|
+
email: 'dev@example.com'
|
|
59
|
+
},
|
|
60
|
+
user: 'users'
|
|
61
|
+
},
|
|
62
|
+
auth: {
|
|
63
|
+
jwtOrder: [
|
|
64
|
+
'Bearer'
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
cookiePrefix: 'payload',
|
|
68
|
+
csrf: []
|
|
69
|
+
},
|
|
70
|
+
find: vi.fn(async ({ depth, req })=>{
|
|
71
|
+
if (req?.query) {
|
|
72
|
+
req.query.depth = depth;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
docs: [
|
|
76
|
+
{
|
|
77
|
+
id: 'auto-login-user'
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
}),
|
|
82
|
+
findByID: async ({ collection, id })=>({
|
|
83
|
+
id,
|
|
84
|
+
role: collection === 'posts' ? 'admin' : 'user'
|
|
85
|
+
}),
|
|
86
|
+
secret
|
|
87
|
+
});
|
|
88
|
+
const authenticate = async (token, payload = createPayload())=>{
|
|
89
|
+
return JWTAuthentication({
|
|
90
|
+
headers: new Headers({
|
|
91
|
+
Authorization: `Bearer ${token}`,
|
|
92
|
+
DisableAutologin: 'true'
|
|
93
|
+
}),
|
|
94
|
+
payload
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
describe('JWTAuthentication', ()=>{
|
|
98
|
+
it.each([
|
|
99
|
+
{
|
|
100
|
+
expectedDepth: 2,
|
|
101
|
+
isGraphQL: false,
|
|
102
|
+
label: 'REST'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
expectedDepth: 0,
|
|
106
|
+
isGraphQL: true,
|
|
107
|
+
label: 'GraphQL'
|
|
108
|
+
}
|
|
109
|
+
])('should use the expected auth depth for $label auto-login', async ({ expectedDepth, isGraphQL })=>{
|
|
110
|
+
const payload = createPayload();
|
|
111
|
+
const req = {
|
|
112
|
+
fallbackLocale: false,
|
|
113
|
+
locale: 'en',
|
|
114
|
+
query: {
|
|
115
|
+
depth: 9
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
payload.collections.users.config.auth.depth = 2;
|
|
119
|
+
const result = await JWTAuthentication({
|
|
120
|
+
headers: new Headers(),
|
|
121
|
+
isGraphQL,
|
|
122
|
+
payload,
|
|
123
|
+
req
|
|
124
|
+
});
|
|
125
|
+
expect(payload.find).toHaveBeenCalledWith(expect.objectContaining({
|
|
126
|
+
depth: expectedDepth,
|
|
127
|
+
fallbackLocale: false,
|
|
128
|
+
locale: 'en',
|
|
129
|
+
req
|
|
130
|
+
}));
|
|
131
|
+
expect(req.query.depth).toBe(9);
|
|
132
|
+
expect(result.user).toMatchObject({
|
|
133
|
+
_strategy: 'local-jwt',
|
|
134
|
+
collection: 'users',
|
|
135
|
+
id: 'auto-login-user'
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
it('should authenticate flat v3 token data with a protected-header version marker', async ()=>{
|
|
139
|
+
const { token } = await jwtSign({
|
|
140
|
+
fieldsToSign: {
|
|
141
|
+
collection: 'users',
|
|
142
|
+
id: 'user-id',
|
|
143
|
+
role: 'user'
|
|
144
|
+
},
|
|
145
|
+
secret,
|
|
146
|
+
tokenExpiration: 3600
|
|
147
|
+
});
|
|
148
|
+
await expect(authenticate(token)).resolves.toMatchObject({
|
|
149
|
+
user: {
|
|
150
|
+
_strategy: 'local-jwt',
|
|
151
|
+
collection: 'users',
|
|
152
|
+
id: 'user-id'
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
it('should reject existing tokens without a protected-header version marker', async ()=>{
|
|
157
|
+
await expect(authenticate(await signToken({
|
|
158
|
+
collection: 'users',
|
|
159
|
+
id: 'user-id',
|
|
160
|
+
role: 'user'
|
|
161
|
+
}, {}))).resolves.toEqual({
|
|
162
|
+
user: null
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
it('should reject tokens with a payload version value but no protected-header marker', async ()=>{
|
|
166
|
+
await expect(authenticate(await signToken({
|
|
167
|
+
collection: 'users',
|
|
168
|
+
id: 'user-id',
|
|
169
|
+
authVersion: JWT_AUTH_VERSION,
|
|
170
|
+
role: 'user'
|
|
171
|
+
}, {}))).resolves.toEqual({
|
|
172
|
+
user: null
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
it('should reject tokens with an invalid protected-header version', async ()=>{
|
|
176
|
+
await expect(authenticate(await signToken({
|
|
177
|
+
collection: 'users',
|
|
178
|
+
id: 'user-id'
|
|
179
|
+
}, {
|
|
180
|
+
authVersion: JWT_AUTH_VERSION + 1
|
|
181
|
+
}))).resolves.toEqual({
|
|
182
|
+
user: null
|
|
183
|
+
});
|
|
184
|
+
await expect(authenticate(await signToken({
|
|
185
|
+
collection: 'users',
|
|
186
|
+
id: 'user-id'
|
|
187
|
+
}, {
|
|
188
|
+
authVersion: '1'
|
|
189
|
+
}))).resolves.toEqual({
|
|
190
|
+
user: null
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
it('should reject inherited collection names', async ()=>{
|
|
194
|
+
const payload = createPayload();
|
|
195
|
+
Object.setPrototypeOf(payload.collections, {
|
|
196
|
+
toString: payload.collections.users
|
|
197
|
+
});
|
|
198
|
+
await expect(authenticate(await signToken({
|
|
199
|
+
collection: 'toString',
|
|
200
|
+
id: 'user-id'
|
|
201
|
+
}), payload)).resolves.toEqual({
|
|
202
|
+
user: null
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
it('should accept numeric identifier zero', async ()=>{
|
|
206
|
+
await expect(authenticate(await signToken({
|
|
207
|
+
collection: 'users',
|
|
208
|
+
id: 0
|
|
209
|
+
}))).resolves.toMatchObject({
|
|
210
|
+
user: {
|
|
211
|
+
id: 0
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
it('should reject tokens targeting a non-auth collection', async ()=>{
|
|
216
|
+
await expect(authenticate(await signToken({
|
|
217
|
+
collection: 'posts',
|
|
218
|
+
id: 'post-id'
|
|
219
|
+
}))).resolves.toEqual({
|
|
220
|
+
user: null
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
it('should reject tokens targeting a collection with local auth disabled', async ()=>{
|
|
224
|
+
await expect(authenticate(await signToken({
|
|
225
|
+
collection: 'disabledUsers',
|
|
226
|
+
id: 'user-id'
|
|
227
|
+
}))).resolves.toEqual({
|
|
228
|
+
user: null
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
it('should reject malformed identity fields', async ()=>{
|
|
232
|
+
await expect(authenticate(await signToken({
|
|
233
|
+
collection: 'users',
|
|
234
|
+
id: {
|
|
235
|
+
value: 'user-id'
|
|
236
|
+
}
|
|
237
|
+
}))).resolves.toEqual({
|
|
238
|
+
user: null
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
it('should ignore saveToJWT aliases that target Payload-owned fields end to end', async ()=>{
|
|
242
|
+
const collectionConfig = {
|
|
243
|
+
fields: [
|
|
244
|
+
{
|
|
245
|
+
name: 'impersonatedID',
|
|
246
|
+
saveToJWT: 'id',
|
|
247
|
+
type: 'text'
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'impersonatedCollection',
|
|
251
|
+
saveToJWT: 'collection',
|
|
252
|
+
type: 'text'
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
slug: 'users'
|
|
256
|
+
};
|
|
257
|
+
const fieldsToSign = getFieldsToSign({
|
|
258
|
+
collectionConfig,
|
|
259
|
+
email: 'attacker@example.com',
|
|
260
|
+
user: {
|
|
261
|
+
id: 'attacker-id',
|
|
262
|
+
impersonatedCollection: 'admins',
|
|
263
|
+
impersonatedID: 'victim-id'
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
const { token } = await jwtSign({
|
|
267
|
+
fieldsToSign,
|
|
268
|
+
secret,
|
|
269
|
+
tokenExpiration: 3600
|
|
270
|
+
});
|
|
271
|
+
const result = await authenticate(token);
|
|
272
|
+
expect(result.user).toMatchObject({
|
|
273
|
+
collection: 'users',
|
|
274
|
+
id: 'attacker-id'
|
|
275
|
+
});
|
|
276
|
+
expect(result.user?.collection).not.toBe('admins');
|
|
277
|
+
expect(result.user?.id).not.toBe('victim-id');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
//# sourceMappingURL=jwt.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/auth/strategies/jwt.spec.ts"],"sourcesContent":["import type { CollectionConfig } from '../../collections/config/types.js'\nimport type { Payload, PayloadRequest } from '../../types/index.js'\n\nimport { SignJWT } from 'jose'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport { getFieldsToSign } from '../getFieldsToSign.js'\nimport { jwtSign } from '../jwt.js'\nimport { JWT_AUTH_VERSION } from '../jwtAuth.js'\nimport { JWTAuthentication } from './jwt.js'\n\nconst secret = '01234567890123456789012345678901'\n\nconst signToken = async (\n tokenData: Record<string, unknown>,\n protectedHeader: { authVersion?: number | string } = {\n authVersion: JWT_AUTH_VERSION,\n },\n): Promise<string> =>\n new SignJWT(tokenData)\n .setProtectedHeader({ alg: 'HS256', typ: 'JWT', ...protectedHeader })\n .sign(new TextEncoder().encode(secret))\n\nconst createPayload = (): Payload =>\n ({\n collections: {\n admins: {\n config: {\n auth: {\n depth: 0,\n useSessions: false,\n verify: false,\n },\n slug: 'admins',\n },\n },\n disabledUsers: {\n config: {\n auth: {\n depth: 0,\n disableLocalStrategy: true,\n useSessions: false,\n verify: false,\n },\n slug: 'disabledUsers',\n },\n },\n posts: {\n config: {\n auth: false,\n slug: 'posts',\n },\n },\n users: {\n config: {\n auth: {\n depth: 0,\n useSessions: false,\n verify: false,\n },\n slug: 'users',\n },\n },\n },\n config: {\n admin: {\n autoLogin: {\n email: 'dev@example.com',\n },\n user: 'users',\n },\n auth: {\n jwtOrder: ['Bearer'],\n },\n cookiePrefix: 'payload',\n csrf: [],\n },\n find: vi.fn(async ({ depth, req }: { depth: number; req?: PayloadRequest }) => {\n if (req?.query) {\n req.query.depth = depth\n }\n\n return {\n docs: [{ id: 'auto-login-user' }],\n }\n }),\n findByID: async ({ collection, id }) => ({\n id,\n role: collection === 'posts' ? 'admin' : 'user',\n }),\n secret,\n }) as unknown as Payload\n\nconst authenticate = async (token: string, payload = createPayload()) => {\n return JWTAuthentication({\n headers: new Headers({\n Authorization: `Bearer ${token}`,\n DisableAutologin: 'true',\n }),\n payload,\n })\n}\n\ndescribe('JWTAuthentication', () => {\n it.each([\n { expectedDepth: 2, isGraphQL: false, label: 'REST' },\n { expectedDepth: 0, isGraphQL: true, label: 'GraphQL' },\n ])(\n 'should use the expected auth depth for $label auto-login',\n async ({ expectedDepth, isGraphQL }) => {\n const payload = createPayload()\n const req = {\n fallbackLocale: false,\n locale: 'en',\n query: { depth: 9 },\n } as PayloadRequest\n payload.collections.users.config.auth.depth = 2\n\n const result = await JWTAuthentication({\n headers: new Headers(),\n isGraphQL,\n payload,\n req,\n })\n\n expect(payload.find).toHaveBeenCalledWith(\n expect.objectContaining({\n depth: expectedDepth,\n fallbackLocale: false,\n locale: 'en',\n req,\n }),\n )\n expect(req.query.depth).toBe(9)\n expect(result.user).toMatchObject({\n _strategy: 'local-jwt',\n collection: 'users',\n id: 'auto-login-user',\n })\n },\n )\n\n it('should authenticate flat v3 token data with a protected-header version marker', async () => {\n const { token } = await jwtSign({\n fieldsToSign: {\n collection: 'users',\n id: 'user-id',\n role: 'user',\n },\n secret,\n tokenExpiration: 3600,\n })\n\n await expect(authenticate(token)).resolves.toMatchObject({\n user: {\n _strategy: 'local-jwt',\n collection: 'users',\n id: 'user-id',\n },\n })\n })\n\n it('should reject existing tokens without a protected-header version marker', async () => {\n await expect(\n authenticate(\n await signToken(\n {\n collection: 'users',\n id: 'user-id',\n role: 'user',\n },\n {},\n ),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject tokens with a payload version value but no protected-header marker', async () => {\n await expect(\n authenticate(\n await signToken(\n {\n collection: 'users',\n id: 'user-id',\n authVersion: JWT_AUTH_VERSION,\n role: 'user',\n },\n {},\n ),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject tokens with an invalid protected-header version', async () => {\n await expect(\n authenticate(\n await signToken(\n {\n collection: 'users',\n id: 'user-id',\n },\n { authVersion: JWT_AUTH_VERSION + 1 },\n ),\n ),\n ).resolves.toEqual({ user: null })\n\n await expect(\n authenticate(\n await signToken(\n {\n collection: 'users',\n id: 'user-id',\n },\n { authVersion: '1' },\n ),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject inherited collection names', async () => {\n const payload = createPayload()\n Object.setPrototypeOf(payload.collections, { toString: payload.collections.users })\n\n await expect(\n authenticate(\n await signToken({\n collection: 'toString',\n id: 'user-id',\n }),\n payload,\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should accept numeric identifier zero', async () => {\n await expect(\n authenticate(\n await signToken({\n collection: 'users',\n id: 0,\n }),\n ),\n ).resolves.toMatchObject({ user: { id: 0 } })\n })\n\n it('should reject tokens targeting a non-auth collection', async () => {\n await expect(\n authenticate(\n await signToken({\n collection: 'posts',\n id: 'post-id',\n }),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject tokens targeting a collection with local auth disabled', async () => {\n await expect(\n authenticate(\n await signToken({\n collection: 'disabledUsers',\n id: 'user-id',\n }),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject malformed identity fields', async () => {\n await expect(\n authenticate(\n await signToken({\n collection: 'users',\n id: { value: 'user-id' },\n }),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should ignore saveToJWT aliases that target Payload-owned fields end to end', async () => {\n const collectionConfig = {\n fields: [\n {\n name: 'impersonatedID',\n saveToJWT: 'id',\n type: 'text',\n },\n {\n name: 'impersonatedCollection',\n saveToJWT: 'collection',\n type: 'text',\n },\n ],\n slug: 'users',\n } as CollectionConfig\n\n const fieldsToSign = getFieldsToSign({\n collectionConfig,\n email: 'attacker@example.com',\n user: {\n id: 'attacker-id',\n impersonatedCollection: 'admins',\n impersonatedID: 'victim-id',\n } as PayloadRequest['user'],\n })\n const { token } = await jwtSign({\n fieldsToSign,\n secret,\n tokenExpiration: 3600,\n })\n const result = await authenticate(token)\n\n expect(result.user).toMatchObject({\n collection: 'users',\n id: 'attacker-id',\n })\n expect(result.user?.collection).not.toBe('admins')\n expect(result.user?.id).not.toBe('victim-id')\n })\n})\n"],"names":["SignJWT","describe","expect","it","vi","getFieldsToSign","jwtSign","JWT_AUTH_VERSION","JWTAuthentication","secret","signToken","tokenData","protectedHeader","authVersion","setProtectedHeader","alg","typ","sign","TextEncoder","encode","createPayload","collections","admins","config","auth","depth","useSessions","verify","slug","disabledUsers","disableLocalStrategy","posts","users","admin","autoLogin","email","user","jwtOrder","cookiePrefix","csrf","find","fn","req","query","docs","id","findByID","collection","role","authenticate","token","payload","headers","Headers","Authorization","DisableAutologin","each","expectedDepth","isGraphQL","label","fallbackLocale","locale","result","toHaveBeenCalledWith","objectContaining","toBe","toMatchObject","_strategy","fieldsToSign","tokenExpiration","resolves","toEqual","Object","setPrototypeOf","toString","value","collectionConfig","fields","name","saveToJWT","type","impersonatedCollection","impersonatedID","not"],"mappings":"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"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { TypeWithID } from '../../../collections/config/types.js';
|
|
2
2
|
type Doc = Record<string, unknown> & TypeWithID;
|
|
3
|
+
type AuthenticationResult = {
|
|
4
|
+
doc: Doc;
|
|
5
|
+
shouldUpdatePasswordHash: boolean;
|
|
6
|
+
};
|
|
3
7
|
type Args = {
|
|
4
8
|
doc: Doc;
|
|
5
9
|
password: string;
|
|
6
10
|
};
|
|
7
|
-
export declare const authenticateLocalStrategy: ({ doc, password }: Args) => Promise<
|
|
11
|
+
export declare const authenticateLocalStrategy: ({ doc, password, }: Args) => Promise<AuthenticationResult | null>;
|
|
8
12
|
export {};
|
|
9
13
|
//# sourceMappingURL=authenticate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.d.ts","sourceRoot":"","sources":["../../../../src/auth/strategies/local/authenticate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAA;
|
|
1
|
+
{"version":3,"file":"authenticate.d.ts","sourceRoot":"","sources":["../../../../src/auth/strategies/local/authenticate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAA;AAItE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAAA;AAE/C,KAAK,oBAAoB,GAAG;IAC1B,GAAG,EAAE,GAAG,CAAA;IACR,wBAAwB,EAAE,OAAO,CAAA;CAClC,CAAA;AAED,KAAK,IAAI,GAAG;IACV,GAAG,EAAE,GAAG,CAAA;IACR,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,eAAO,MAAM,yBAAyB,uBAGnC,IAAI,KAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAmC5C,CAAA"}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
// @ts-strict-ignore
|
|
2
2
|
import crypto from 'crypto';
|
|
3
|
+
import { getPasswordHashParameters, isCurrentPasswordHash } from './generatePasswordSaltHash.js';
|
|
3
4
|
export const authenticateLocalStrategy = async ({ doc, password })=>{
|
|
4
5
|
try {
|
|
5
6
|
const { hash, salt } = doc;
|
|
6
7
|
if (typeof salt === 'string' && typeof hash === 'string') {
|
|
8
|
+
const { hash: storedHash, iterations, keyLength } = getPasswordHashParameters(hash);
|
|
7
9
|
const res = await new Promise((resolve, reject)=>{
|
|
8
|
-
crypto.pbkdf2(password, salt,
|
|
10
|
+
crypto.pbkdf2(password, salt, iterations, keyLength, 'sha256', (e, hashBuffer)=>{
|
|
9
11
|
if (e) {
|
|
10
12
|
reject(e);
|
|
11
13
|
}
|
|
12
|
-
const storedHashBuffer = Buffer.from(
|
|
14
|
+
const storedHashBuffer = Buffer.from(storedHash, 'hex');
|
|
13
15
|
if (hashBuffer.length === storedHashBuffer.length && crypto.timingSafeEqual(hashBuffer, storedHashBuffer)) {
|
|
14
|
-
resolve(
|
|
16
|
+
resolve({
|
|
17
|
+
doc,
|
|
18
|
+
shouldUpdatePasswordHash: !isCurrentPasswordHash(hash)
|
|
19
|
+
});
|
|
15
20
|
} else {
|
|
16
21
|
reject(new Error('Invalid password'));
|
|
17
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/auth/strategies/local/authenticate.ts"],"sourcesContent":["// @ts-strict-ignore\nimport crypto from 'crypto'\n\nimport type { TypeWithID } from '../../../collections/config/types.js'\n\ntype Doc = Record<string, unknown> & TypeWithID\n\ntype Args = {\n doc: Doc\n password: string\n}\n\nexport const authenticateLocalStrategy = async ({
|
|
1
|
+
{"version":3,"sources":["../../../../src/auth/strategies/local/authenticate.ts"],"sourcesContent":["// @ts-strict-ignore\nimport crypto from 'crypto'\n\nimport type { TypeWithID } from '../../../collections/config/types.js'\n\nimport { getPasswordHashParameters, isCurrentPasswordHash } from './generatePasswordSaltHash.js'\n\ntype Doc = Record<string, unknown> & TypeWithID\n\ntype AuthenticationResult = {\n doc: Doc\n shouldUpdatePasswordHash: boolean\n}\n\ntype Args = {\n doc: Doc\n password: string\n}\n\nexport const authenticateLocalStrategy = async ({\n doc,\n password,\n}: Args): Promise<AuthenticationResult | null> => {\n try {\n const { hash, salt } = doc\n\n if (typeof salt === 'string' && typeof hash === 'string') {\n const { hash: storedHash, iterations, keyLength } = getPasswordHashParameters(hash)\n const res = await new Promise<AuthenticationResult | null>((resolve, reject) => {\n crypto.pbkdf2(password, salt, iterations, keyLength, 'sha256', (e, hashBuffer) => {\n if (e) {\n reject(e)\n }\n\n const storedHashBuffer = Buffer.from(storedHash, 'hex')\n\n if (\n hashBuffer.length === storedHashBuffer.length &&\n crypto.timingSafeEqual(hashBuffer, storedHashBuffer)\n ) {\n resolve({\n doc,\n shouldUpdatePasswordHash: !isCurrentPasswordHash(hash),\n })\n } else {\n reject(new Error('Invalid password'))\n }\n })\n })\n\n return res\n }\n\n return null\n } catch (ignore) {\n return null\n }\n}\n"],"names":["crypto","getPasswordHashParameters","isCurrentPasswordHash","authenticateLocalStrategy","doc","password","hash","salt","storedHash","iterations","keyLength","res","Promise","resolve","reject","pbkdf2","e","hashBuffer","storedHashBuffer","Buffer","from","length","timingSafeEqual","shouldUpdatePasswordHash","Error","ignore"],"mappings":"AAAA,oBAAoB;AACpB,OAAOA,YAAY,SAAQ;AAI3B,SAASC,yBAAyB,EAAEC,qBAAqB,QAAQ,gCAA+B;AAchG,OAAO,MAAMC,4BAA4B,OAAO,EAC9CC,GAAG,EACHC,QAAQ,EACH;IACL,IAAI;QACF,MAAM,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGH;QAEvB,IAAI,OAAOG,SAAS,YAAY,OAAOD,SAAS,UAAU;YACxD,MAAM,EAAEA,MAAME,UAAU,EAAEC,UAAU,EAAEC,SAAS,EAAE,GAAGT,0BAA0BK;YAC9E,MAAMK,MAAM,MAAM,IAAIC,QAAqC,CAACC,SAASC;gBACnEd,OAAOe,MAAM,CAACV,UAAUE,MAAME,YAAYC,WAAW,UAAU,CAACM,GAAGC;oBACjE,IAAID,GAAG;wBACLF,OAAOE;oBACT;oBAEA,MAAME,mBAAmBC,OAAOC,IAAI,CAACZ,YAAY;oBAEjD,IACES,WAAWI,MAAM,KAAKH,iBAAiBG,MAAM,IAC7CrB,OAAOsB,eAAe,CAACL,YAAYC,mBACnC;wBACAL,QAAQ;4BACNT;4BACAmB,0BAA0B,CAACrB,sBAAsBI;wBACnD;oBACF,OAAO;wBACLQ,OAAO,IAAIU,MAAM;oBACnB;gBACF;YACF;YAEA,OAAOb;QACT;QAEA,OAAO;IACT,EAAE,OAAOc,QAAQ;QACf,OAAO;IACT;AACF,EAAC"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
3
|
import { authenticateLocalStrategy } from './authenticate.js';
|
|
4
|
-
|
|
5
|
-
const generateHashAndSalt = (password)=>{
|
|
4
|
+
const generateExistingHashAndSalt = (password)=>{
|
|
6
5
|
const salt = crypto.randomBytes(32).toString('hex');
|
|
7
6
|
const hash = crypto.pbkdf2Sync(password, salt, 25000, 512, 'sha256').toString('hex');
|
|
8
7
|
return {
|
|
@@ -10,10 +9,35 @@ const generateHashAndSalt = (password)=>{
|
|
|
10
9
|
salt
|
|
11
10
|
};
|
|
12
11
|
};
|
|
12
|
+
const generateCurrentHashAndSalt = (password)=>{
|
|
13
|
+
const salt = crypto.randomBytes(32).toString('hex');
|
|
14
|
+
const hash = crypto.pbkdf2Sync(password, salt, 600000, 32, 'sha256').toString('hex');
|
|
15
|
+
return {
|
|
16
|
+
hash: `pbkdf2-sha256-v1:${hash}`,
|
|
17
|
+
salt
|
|
18
|
+
};
|
|
19
|
+
};
|
|
13
20
|
describe('authenticateLocalStrategy', ()=>{
|
|
14
|
-
it('should return doc
|
|
21
|
+
it('should return doc for an existing password hash', async ()=>{
|
|
22
|
+
const password = 'test-password';
|
|
23
|
+
const { hash, salt } = generateExistingHashAndSalt(password);
|
|
24
|
+
const doc = {
|
|
25
|
+
id: 1,
|
|
26
|
+
hash,
|
|
27
|
+
salt
|
|
28
|
+
};
|
|
29
|
+
const result = await authenticateLocalStrategy({
|
|
30
|
+
doc,
|
|
31
|
+
password
|
|
32
|
+
});
|
|
33
|
+
expect(result).toEqual({
|
|
34
|
+
doc,
|
|
35
|
+
shouldUpdatePasswordHash: true
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
it('should return doc for a current password hash', async ()=>{
|
|
15
39
|
const password = 'test-password';
|
|
16
|
-
const { hash, salt } =
|
|
40
|
+
const { hash, salt } = generateCurrentHashAndSalt(password);
|
|
17
41
|
const doc = {
|
|
18
42
|
id: 1,
|
|
19
43
|
hash,
|
|
@@ -23,10 +47,13 @@ describe('authenticateLocalStrategy', ()=>{
|
|
|
23
47
|
doc,
|
|
24
48
|
password
|
|
25
49
|
});
|
|
26
|
-
expect(result).toEqual(
|
|
50
|
+
expect(result).toEqual({
|
|
51
|
+
doc,
|
|
52
|
+
shouldUpdatePasswordHash: false
|
|
53
|
+
});
|
|
27
54
|
});
|
|
28
55
|
it('should return null when password is invalid', async ()=>{
|
|
29
|
-
const { hash, salt } =
|
|
56
|
+
const { hash, salt } = generateExistingHashAndSalt('correct-password');
|
|
30
57
|
const doc = {
|
|
31
58
|
id: 1,
|
|
32
59
|
hash,
|
|
@@ -39,7 +66,7 @@ describe('authenticateLocalStrategy', ()=>{
|
|
|
39
66
|
expect(result).toBeNull();
|
|
40
67
|
});
|
|
41
68
|
it('should return null when salt is missing', async ()=>{
|
|
42
|
-
const { hash } =
|
|
69
|
+
const { hash } = generateExistingHashAndSalt('test-password');
|
|
43
70
|
const doc = {
|
|
44
71
|
id: 1,
|
|
45
72
|
hash
|
|
@@ -51,7 +78,7 @@ describe('authenticateLocalStrategy', ()=>{
|
|
|
51
78
|
expect(result).toBeNull();
|
|
52
79
|
});
|
|
53
80
|
it('should return null when hash is missing', async ()=>{
|
|
54
|
-
const { salt } =
|
|
81
|
+
const { salt } = generateExistingHashAndSalt('test-password');
|
|
55
82
|
const doc = {
|
|
56
83
|
id: 1,
|
|
57
84
|
salt
|
|
@@ -64,8 +91,8 @@ describe('authenticateLocalStrategy', ()=>{
|
|
|
64
91
|
});
|
|
65
92
|
it('should return null when hash has different length (tampered)', async ()=>{
|
|
66
93
|
const password = 'test-password';
|
|
67
|
-
const { salt } =
|
|
68
|
-
// Truncated hash - different length than
|
|
94
|
+
const { salt } = generateExistingHashAndSalt(password);
|
|
95
|
+
// Truncated hash - different length than an existing hash derives
|
|
69
96
|
const shortHash = 'abcd1234';
|
|
70
97
|
const doc = {
|
|
71
98
|
id: 1,
|