feat: exports additional login helper utils (#12309)

Exports a few utilities that are used internally to the login operation,
but could be helpful for others building plugins.

Specifically:

- `isUserLocked` - a check to ensure that a given user is not locked due
to too many invalid attempts
- `checkLoginPermissions` - checks to see that the user is not locked as
well as that it is properly verified, if applicable
- `jwtSign` - Payload's internal JWT signing approach
- `getFieldsToSign` - reduce down a document's fields for JWT creation
based on collection config settings
- `incrementLoginAttempts` / `resetLoginAttempts` - utilities to handle
both failed and successful login attempts
- `UnverifiedEmail` - an error that could be thrown if attempting to log
in to an account without prior successful email verification
This commit is contained in:
James Mikrut
2025-05-05 10:23:01 -04:00
committed by GitHub
parent 446938b9cb
commit dcd4e37ccc
4 changed files with 49 additions and 22 deletions

View File

@@ -1,7 +0,0 @@
const isLocked = (date: number): boolean => {
if (!date) {
return false
}
return date > Date.now()
}
export default isLocked

View File

@@ -0,0 +1,6 @@
export const isUserLocked = (date: number): boolean => {
if (!date) {
return false
}
return date > Date.now()
}

View File

@@ -3,6 +3,7 @@ import type {
AuthOperationsFromCollectionSlug,
Collection,
DataFromCollectionSlug,
SanitizedCollectionConfig,
} from '../../collections/config/types.js'
import type { CollectionSlug } from '../../index.js'
import type { PayloadRequest, Where } from '../../types/index.js'
@@ -21,7 +22,7 @@ import { killTransaction } from '../../utilities/killTransaction.js'
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
import { getFieldsToSign } from '../getFieldsToSign.js'
import { getLoginOptions } from '../getLoginOptions.js'
import isLocked from '../isLocked.js'
import { isUserLocked } from '../isUserLocked.js'
import { jwtSign } from '../jwt.js'
import { authenticateLocalStrategy } from '../strategies/local/authenticate.js'
import { incrementLoginAttempts } from '../strategies/local/incrementLoginAttempts.js'
@@ -42,6 +43,32 @@ export type Arguments<TSlug extends CollectionSlug> = {
showHiddenFields?: boolean
}
type CheckLoginPermissionArgs = {
collection: SanitizedCollectionConfig
loggingInWithUsername?: boolean
req: PayloadRequest
user: any
}
export const checkLoginPermission = ({
collection,
loggingInWithUsername,
req,
user,
}: CheckLoginPermissionArgs) => {
if (!user) {
throw new AuthenticationError(req.t, Boolean(loggingInWithUsername))
}
if (collection.auth.verify && user._verified === false) {
throw new UnverifiedEmail({ t: req.t })
}
if (isUserLocked(new Date(user.lockUntil).getTime())) {
throw new LockedAuth(req.t)
}
}
export const loginOperation = async <TSlug extends CollectionSlug>(
incomingArgs: Arguments<TSlug>,
): Promise<{ user: DataFromCollectionSlug<TSlug> } & Result> => {
@@ -184,21 +211,16 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
where: whereConstraint,
})
if (!user) {
throw new AuthenticationError(req.t, Boolean(canLoginWithUsername && sanitizedUsername))
}
if (args.collection.config.auth.verify && user._verified === false) {
throw new UnverifiedEmail({ t: req.t })
}
checkLoginPermission({
collection: collectionConfig,
loggingInWithUsername: Boolean(canLoginWithUsername && sanitizedUsername),
req,
user,
})
user.collection = collectionConfig.slug
user._strategy = 'local-jwt'
if (isLocked(new Date(user.lockUntil).getTime())) {
throw new LockedAuth(req.t)
}
const authResult = await authenticateLocalStrategy({ doc: user, password })
user = sanitizeInternalFields(user)

View File

@@ -89,6 +89,10 @@ import { traverseFields } from './utilities/traverseFields.js'
export { default as executeAccess } from './auth/executeAccess.js'
export { executeAuthStrategies } from './auth/executeAuthStrategies.js'
export { extractAccessFromPermission } from './auth/extractAccessFromPermission.js'
export { getAccessResults } from './auth/getAccessResults.js'
export { getFieldsToSign } from './auth/getFieldsToSign.js'
export { getLoginOptions } from './auth/getLoginOptions.js'
export interface GeneratedTypes {
authUntyped: {
@@ -977,13 +981,12 @@ interface RequestContext {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface DatabaseAdapter extends BaseDatabaseAdapter {}
export type { Payload, RequestContext }
export { extractAccessFromPermission } from './auth/extractAccessFromPermission.js'
export { getAccessResults } from './auth/getAccessResults.js'
export { getFieldsToSign } from './auth/getFieldsToSign.js'
export * from './auth/index.js'
export { jwtSign } from './auth/jwt.js'
export { accessOperation } from './auth/operations/access.js'
export { forgotPasswordOperation } from './auth/operations/forgotPassword.js'
export { initOperation } from './auth/operations/init.js'
export { checkLoginPermission } from './auth/operations/login.js'
export { loginOperation } from './auth/operations/login.js'
export { logoutOperation } from './auth/operations/logout.js'
export type { MeOperationResult } from './auth/operations/me.js'
@@ -994,6 +997,8 @@ export { resetPasswordOperation } from './auth/operations/resetPassword.js'
export { unlockOperation } from './auth/operations/unlock.js'
export { verifyEmailOperation } from './auth/operations/verifyEmail.js'
export { JWTAuthentication } from './auth/strategies/jwt.js'
export { incrementLoginAttempts } from './auth/strategies/local/incrementLoginAttempts.js'
export { resetLoginAttempts } from './auth/strategies/local/resetLoginAttempts.js'
export type {
AuthStrategyFunction,
AuthStrategyFunctionArgs,
@@ -1201,6 +1206,7 @@ export {
MissingFile,
NotFound,
QueryError,
UnverifiedEmail,
ValidationError,
ValidationErrorName,
} from './errors/index.js'