@warlock.js/auth 5.29.0 → 5.30.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/CHANGELOG.md +12 -0
- package/esm/commands/auth-cleanup-command.d.mts +1 -1
- package/esm/commands/auth-cleanup-command.d.mts.map +1 -1
- package/esm/commands/auth-cleanup-command.mjs +1 -1
- package/esm/commands/auth-cleanup-command.mjs.map +1 -1
- package/esm/commands/auth-purge-never-expiring-command.d.mts +1 -1
- package/esm/commands/auth-purge-never-expiring-command.d.mts.map +1 -1
- package/esm/commands/auth-purge-never-expiring-command.mjs +1 -1
- package/esm/commands/auth-purge-never-expiring-command.mjs.map +1 -1
- package/esm/commands/jwt-secret-generator-command.d.mts +1 -1
- package/esm/commands/jwt-secret-generator-command.d.mts.map +1 -1
- package/esm/commands/jwt-secret-generator-command.mjs +1 -1
- package/esm/commands/jwt-secret-generator-command.mjs.map +1 -1
- package/esm/contracts/auth-contract.d.mts +1 -1
- package/esm/contracts/auth-contract.d.mts.map +1 -1
- package/esm/contracts/providers.d.mts +1 -1
- package/esm/contracts/providers.d.mts.map +1 -1
- package/esm/contracts/request-locals.d.mts +1 -1
- package/esm/contracts/request-locals.d.mts.map +1 -1
- package/esm/contracts/types.d.mts +1 -1
- package/esm/contracts/types.d.mts.map +1 -1
- package/esm/contracts/types.mjs +1 -1
- package/esm/contracts/types.mjs.map +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.d.mts +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.d.mts.map +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.mjs +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.mjs.map +1 -1
- package/esm/errors/email-not-verified.error.d.mts +1 -1
- package/esm/errors/email-not-verified.error.d.mts.map +1 -1
- package/esm/errors/email-not-verified.error.mjs +1 -1
- package/esm/errors/email-not-verified.error.mjs.map +1 -1
- package/esm/errors/invalid-one-time-token.error.d.mts +1 -1
- package/esm/errors/invalid-one-time-token.error.d.mts.map +1 -1
- package/esm/errors/invalid-one-time-token.error.mjs +1 -1
- package/esm/errors/invalid-one-time-token.error.mjs.map +1 -1
- package/esm/errors/invalid-passkey.error.d.mts +1 -1
- package/esm/errors/invalid-passkey.error.d.mts.map +1 -1
- package/esm/errors/invalid-passkey.error.mjs +1 -1
- package/esm/errors/invalid-passkey.error.mjs.map +1 -1
- package/esm/errors/invalid-provider-callback.error.d.mts +1 -1
- package/esm/errors/invalid-provider-callback.error.d.mts.map +1 -1
- package/esm/errors/invalid-provider-callback.error.mjs +1 -1
- package/esm/errors/invalid-provider-callback.error.mjs.map +1 -1
- package/esm/errors/notifications-unavailable.error.d.mts +1 -1
- package/esm/errors/notifications-unavailable.error.d.mts.map +1 -1
- package/esm/errors/notifications-unavailable.error.mjs +1 -1
- package/esm/errors/notifications-unavailable.error.mjs.map +1 -1
- package/esm/errors/provider-email-not-verified.error.d.mts +1 -1
- package/esm/errors/provider-email-not-verified.error.d.mts.map +1 -1
- package/esm/errors/provider-email-not-verified.error.mjs +1 -1
- package/esm/errors/provider-email-not-verified.error.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/index.mjs +2 -2
- package/esm/localization/ar.translations.mjs +1 -1
- package/esm/localization/ar.translations.mjs.map +1 -1
- package/esm/localization/auth-default-translations.d.mts +1 -1
- package/esm/localization/auth-default-translations.d.mts.map +1 -1
- package/esm/localization/auth-default-translations.mjs +1 -1
- package/esm/localization/auth-default-translations.mjs.map +1 -1
- package/esm/localization/auth-translations.type.d.mts +1 -1
- package/esm/localization/auth-translations.type.d.mts.map +1 -1
- package/esm/localization/en.translations.mjs +1 -1
- package/esm/localization/en.translations.mjs.map +1 -1
- package/esm/localization/init.mjs +1 -1
- package/esm/localization/init.mjs.map +1 -1
- package/esm/localization/register-auth-translations.d.mts +1 -1
- package/esm/localization/register-auth-translations.d.mts.map +1 -1
- package/esm/localization/register-auth-translations.mjs +1 -1
- package/esm/localization/register-auth-translations.mjs.map +1 -1
- package/esm/middleware/auth.middleware.d.mts +1 -1
- package/esm/middleware/auth.middleware.d.mts.map +1 -1
- package/esm/middleware/auth.middleware.mjs +1 -1
- package/esm/middleware/auth.middleware.mjs.map +1 -1
- package/esm/middleware/csrf-origin-check.d.mts +1 -1
- package/esm/middleware/csrf-origin-check.d.mts.map +1 -1
- package/esm/middleware/csrf-origin-check.mjs +1 -1
- package/esm/middleware/csrf-origin-check.mjs.map +1 -1
- package/esm/middleware/index.d.mts +1 -1
- package/esm/middleware/index.mjs +1 -1
- package/esm/middleware/localized-login-path.mjs +1 -1
- package/esm/middleware/localized-login-path.mjs.map +1 -1
- package/esm/middleware/login-throttle.middleware.d.mts +17 -2
- package/esm/middleware/login-throttle.middleware.d.mts.map +1 -1
- package/esm/middleware/login-throttle.middleware.mjs +84 -6
- package/esm/middleware/login-throttle.middleware.mjs.map +1 -1
- package/esm/middleware/one-time-token-throttle.middleware.d.mts +1 -1
- package/esm/middleware/one-time-token-throttle.middleware.d.mts.map +1 -1
- package/esm/middleware/one-time-token-throttle.middleware.mjs +1 -1
- package/esm/middleware/one-time-token-throttle.middleware.mjs.map +1 -1
- package/esm/middleware/otp-throttle.middleware.d.mts +1 -1
- package/esm/middleware/otp-throttle.middleware.d.mts.map +1 -1
- package/esm/middleware/otp-throttle.middleware.mjs +1 -1
- package/esm/middleware/otp-throttle.middleware.mjs.map +1 -1
- package/esm/middleware/require-verified-email.middleware.d.mts +1 -1
- package/esm/middleware/require-verified-email.middleware.d.mts.map +1 -1
- package/esm/middleware/require-verified-email.middleware.mjs +1 -1
- package/esm/middleware/require-verified-email.middleware.mjs.map +1 -1
- package/esm/models/access-token/access-token.model.d.mts +27 -35
- package/esm/models/access-token/access-token.model.d.mts.map +1 -1
- package/esm/models/access-token/access-token.model.mjs +1 -1
- package/esm/models/access-token/access-token.model.mjs.map +1 -1
- package/esm/models/access-token/family-association.migration.mjs +1 -1
- package/esm/models/access-token/family-association.migration.mjs.map +1 -1
- package/esm/models/access-token/migration.mjs +1 -1
- package/esm/models/access-token/migration.mjs.map +1 -1
- package/esm/models/auth-token-family/auth-token-family.model.d.mts +27 -43
- package/esm/models/auth-token-family/auth-token-family.model.d.mts.map +1 -1
- package/esm/models/auth-token-family/auth-token-family.model.mjs +1 -1
- package/esm/models/auth-token-family/auth-token-family.model.mjs.map +1 -1
- package/esm/models/auth-token-family/migration.mjs +1 -1
- package/esm/models/auth-token-family/migration.mjs.map +1 -1
- package/esm/models/auth.model.d.mts +1 -1
- package/esm/models/auth.model.d.mts.map +1 -1
- package/esm/models/auth.model.mjs +1 -1
- package/esm/models/auth.model.mjs.map +1 -1
- package/esm/models/index.d.mts +1 -1
- package/esm/models/index.d.mts.map +1 -1
- package/esm/models/index.mjs +1 -1
- package/esm/models/index.mjs.map +1 -1
- package/esm/models/one-time-token/migration.mjs +1 -1
- package/esm/models/one-time-token/migration.mjs.map +1 -1
- package/esm/models/one-time-token/one-time-token.model.d.mts +35 -67
- package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -1
- package/esm/models/one-time-token/one-time-token.model.mjs +1 -1
- package/esm/models/one-time-token/one-time-token.model.mjs.map +1 -1
- package/esm/models/passkey-credential/migration.mjs +1 -1
- package/esm/models/passkey-credential/migration.mjs.map +1 -1
- package/esm/models/passkey-credential/passkey-credential.model.d.mts +31 -47
- package/esm/models/passkey-credential/passkey-credential.model.d.mts.map +1 -1
- package/esm/models/passkey-credential/passkey-credential.model.mjs +1 -1
- package/esm/models/passkey-credential/passkey-credential.model.mjs.map +1 -1
- package/esm/models/provider-account/migration.mjs +1 -1
- package/esm/models/provider-account/migration.mjs.map +1 -1
- package/esm/models/provider-account/provider-account.model.d.mts +27 -35
- package/esm/models/provider-account/provider-account.model.d.mts.map +1 -1
- package/esm/models/provider-account/provider-account.model.mjs +1 -1
- package/esm/models/provider-account/provider-account.model.mjs.map +1 -1
- package/esm/models/refresh-token/migration.mjs +1 -1
- package/esm/models/refresh-token/migration.mjs.map +1 -1
- package/esm/models/refresh-token/refresh-token.model.d.mts +47 -95
- package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
- package/esm/models/refresh-token/refresh-token.model.mjs +1 -1
- package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
- package/esm/models/refresh-token/successor-association.migration.mjs +1 -1
- package/esm/models/refresh-token/successor-association.migration.mjs.map +1 -1
- package/esm/otp/otp.d.mts +1 -1
- package/esm/otp/otp.d.mts.map +1 -1
- package/esm/otp/otp.mjs +1 -1
- package/esm/otp/otp.mjs.map +1 -1
- package/esm/passkeys/passkey-authentication.d.mts +1 -1
- package/esm/passkeys/passkey-authentication.d.mts.map +1 -1
- package/esm/passkeys/passkey-authentication.mjs +1 -1
- package/esm/passkeys/passkey-authentication.mjs.map +1 -1
- package/esm/passkeys/passkey-challenges.mjs +1 -1
- package/esm/passkeys/passkey-challenges.mjs.map +1 -1
- package/esm/passkeys/passkey-registration.d.mts +1 -1
- package/esm/passkeys/passkey-registration.d.mts.map +1 -1
- package/esm/passkeys/passkey-registration.mjs +1 -1
- package/esm/passkeys/passkey-registration.mjs.map +1 -1
- package/esm/passkeys/simplewebauthn.d.mts +1 -1
- package/esm/passkeys/simplewebauthn.d.mts.map +1 -1
- package/esm/passkeys/simplewebauthn.mjs +1 -1
- package/esm/passkeys/simplewebauthn.mjs.map +1 -1
- package/esm/providers/apple-provider.d.mts +1 -1
- package/esm/providers/apple-provider.d.mts.map +1 -1
- package/esm/providers/apple-provider.mjs +1 -1
- package/esm/providers/apple-provider.mjs.map +1 -1
- package/esm/providers/discord-provider.d.mts +1 -1
- package/esm/providers/discord-provider.d.mts.map +1 -1
- package/esm/providers/discord-provider.mjs +1 -1
- package/esm/providers/discord-provider.mjs.map +1 -1
- package/esm/providers/facebook-provider.d.mts +1 -1
- package/esm/providers/facebook-provider.d.mts.map +1 -1
- package/esm/providers/facebook-provider.mjs +1 -1
- package/esm/providers/facebook-provider.mjs.map +1 -1
- package/esm/providers/github-provider.d.mts +1 -1
- package/esm/providers/github-provider.d.mts.map +1 -1
- package/esm/providers/github-provider.mjs +1 -1
- package/esm/providers/github-provider.mjs.map +1 -1
- package/esm/providers/google-provider.d.mts +1 -1
- package/esm/providers/google-provider.d.mts.map +1 -1
- package/esm/providers/google-provider.mjs +1 -1
- package/esm/providers/google-provider.mjs.map +1 -1
- package/esm/providers/linkedin-provider.d.mts +1 -1
- package/esm/providers/linkedin-provider.d.mts.map +1 -1
- package/esm/providers/linkedin-provider.mjs +1 -1
- package/esm/providers/linkedin-provider.mjs.map +1 -1
- package/esm/providers/provider-login.d.mts +1 -1
- package/esm/providers/provider-login.d.mts.map +1 -1
- package/esm/providers/provider-login.mjs +1 -1
- package/esm/providers/provider-login.mjs.map +1 -1
- package/esm/providers/provider-state-cookie.mjs +1 -1
- package/esm/providers/provider-state-cookie.mjs.map +1 -1
- package/esm/providers/resolve-provider-user.mjs +1 -1
- package/esm/providers/resolve-provider-user.mjs.map +1 -1
- package/esm/providers/x-provider.d.mts +1 -1
- package/esm/providers/x-provider.d.mts.map +1 -1
- package/esm/providers/x-provider.mjs +1 -1
- package/esm/providers/x-provider.mjs.map +1 -1
- package/esm/services/auth-config.mjs +1 -1
- package/esm/services/auth-config.mjs.map +1 -1
- package/esm/services/auth-events.d.mts +1 -1
- package/esm/services/auth-events.d.mts.map +1 -1
- package/esm/services/auth-events.mjs +1 -1
- package/esm/services/auth-events.mjs.map +1 -1
- package/esm/services/auth.service.d.mts +1 -1
- package/esm/services/auth.service.d.mts.map +1 -1
- package/esm/services/auth.service.mjs +1 -1
- package/esm/services/auth.service.mjs.map +1 -1
- package/esm/services/current-user.d.mts +1 -1
- package/esm/services/current-user.d.mts.map +1 -1
- package/esm/services/current-user.mjs +1 -1
- package/esm/services/current-user.mjs.map +1 -1
- package/esm/services/email-verification.d.mts +1 -1
- package/esm/services/email-verification.d.mts.map +1 -1
- package/esm/services/email-verification.mjs +1 -1
- package/esm/services/email-verification.mjs.map +1 -1
- package/esm/services/generate-jwt-secret.d.mts +1 -1
- package/esm/services/generate-jwt-secret.d.mts.map +1 -1
- package/esm/services/generate-jwt-secret.mjs +1 -1
- package/esm/services/generate-jwt-secret.mjs.map +1 -1
- package/esm/services/jwt.d.mts +1 -1
- package/esm/services/jwt.d.mts.map +1 -1
- package/esm/services/jwt.mjs +1 -1
- package/esm/services/jwt.mjs.map +1 -1
- package/esm/services/one-time-tokens.d.mts +1 -1
- package/esm/services/one-time-tokens.d.mts.map +1 -1
- package/esm/services/one-time-tokens.mjs +1 -1
- package/esm/services/one-time-tokens.mjs.map +1 -1
- package/esm/services/optional-peer.mjs +1 -1
- package/esm/services/optional-peer.mjs.map +1 -1
- package/esm/services/password-reset.d.mts +1 -1
- package/esm/services/password-reset.d.mts.map +1 -1
- package/esm/services/password-reset.mjs +1 -1
- package/esm/services/password-reset.mjs.map +1 -1
- package/esm/services/resolve-request-user.d.mts +1 -1
- package/esm/services/resolve-request-user.d.mts.map +1 -1
- package/esm/services/resolve-request-user.mjs +1 -1
- package/esm/services/resolve-request-user.mjs.map +1 -1
- package/esm/services/token-family-operation.mjs +1 -1
- package/esm/services/token-family-operation.mjs.map +1 -1
- package/esm/services/token-notifications.mjs +1 -1
- package/esm/services/token-notifications.mjs.map +1 -1
- package/esm/session/page-session.d.mts +1 -1
- package/esm/session/page-session.d.mts.map +1 -1
- package/esm/session/page-session.mjs +1 -1
- package/esm/session/page-session.mjs.map +1 -1
- package/esm/utils/auth-error-codes.d.mts +1 -1
- package/esm/utils/auth-error-codes.d.mts.map +1 -1
- package/esm/utils/auth-error-codes.mjs +1 -1
- package/esm/utils/auth-error-codes.mjs.map +1 -1
- package/esm/utils/safe-equal.d.mts +1 -1
- package/esm/utils/safe-equal.d.mts.map +1 -1
- package/esm/utils/safe-equal.mjs +1 -1
- package/esm/utils/safe-equal.mjs.map +1 -1
- package/esm/utils/token-expiry.d.mts +1 -1
- package/esm/utils/token-expiry.d.mts.map +1 -1
- package/esm/utils/token-expiry.mjs +1 -1
- package/esm/utils/token-expiry.mjs.map +1 -1
- package/llms-full.txt +3 -1
- package/package.json +10 -9
- package/skills/throttle-login-attempts/SKILL.md +3 -1
package/esm/index.d.mts
CHANGED
|
@@ -22,7 +22,7 @@ import { authDefaultTranslations } from "./localization/auth-default-translation
|
|
|
22
22
|
import { registerAuthTranslations } from "./localization/register-auth-translations.mjs";
|
|
23
23
|
import { AUTH_MIDDLEWARE_DESCRIPTOR, AuthMiddlewareDescriptor, AuthMiddlewareDescriptorSource, authMiddleware } from "./middleware/auth.middleware.mjs";
|
|
24
24
|
import { CsrfOriginMismatchError, CsrfOriginMismatchReason, assertCsrfOriginAllowed, requiresCsrfOriginCheck } from "./middleware/csrf-origin-check.mjs";
|
|
25
|
-
import { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware } from "./middleware/login-throttle.middleware.mjs";
|
|
25
|
+
import { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware, throttleIdentifierHash } from "./middleware/login-throttle.middleware.mjs";
|
|
26
26
|
import { tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware } from "./middleware/one-time-token-throttle.middleware.mjs";
|
|
27
27
|
import { otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware } from "./middleware/otp-throttle.middleware.mjs";
|
|
28
28
|
import { requireVerifiedEmail } from "./middleware/require-verified-email.middleware.mjs";
|
|
@@ -54,5 +54,5 @@ import { RequestUserFailure, RequestUserOutcome, ResolveRequestUserOptions, read
|
|
|
54
54
|
import { PageSessionOptions, PageSessionResolver, SessionMiddlewareOptions, SessionResolverOptions, SessionSource, pageSession, sessionMiddleware } from "./session/page-session.mjs";
|
|
55
55
|
import { safeEqual } from "./utils/safe-equal.mjs";
|
|
56
56
|
import { isNeverExpiring, isUsableExpiry, tokenHasExpClaim } from "./utils/token-expiry.mjs";
|
|
57
|
-
export { APPLE_AUTH_FEATURE, AUTH_MIDDLEWARE_DESCRIPTOR, AccessToken, AccessTokenConfig, AccessTokenOutput, AppleJoseModule, AppleProvider, AppleProviderConfig, Auth, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthErrorCodes, AuthErrorTranslations, AuthEventCallback, AuthEventName, AuthEventPayloads, AuthMiddlewareDescriptor, AuthMiddlewareDescriptorSource, AuthMiddlewareOptions, AuthNotification, AuthProvider, AuthProviderSdkMissingError, AuthRefreshCredentialDescriptor, AuthTokenFamily, AuthTranslations, Authenticable, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CookieDomainRequestRequiredError, CookieDomainResolver, CsrfConfig, CsrfOriginMismatchError, CsrfOriginMismatchReason, DeviceInfo, DiscordProvider, DiscordProviderConfig, EmailNotVerifiedError, EmailVerificationConfig, FacebookProvider, FacebookProviderConfig, GOOGLE_AUTH_FEATURE, GitHubProvider, GitHubProviderConfig, GoogleProvider, GoogleProviderConfig, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, IssuedOneTimeToken, JoseModule, LINKEDIN_AUTH_FEATURE, LegacyJwtConfig, LinkedInProvider, LinkedInProviderConfig, LoginResult, LoginThrottleIdentifier, LoginThrottleOptions, LogoutWithoutTokenBehavior, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenPurpose, OneTimeTokenUrlBuilder, OtpConfig, OtpMessage, OtpSender, PageAuthConfig, PageSessionOptions, PageSessionResolver, PasskeyCredential, type PasskeyOptionsJSON, type PasskeyResponseJSON, PasskeysConfig, PasswordResetConfig, PasswordSetter, ProviderAccount, ProviderAuthorizationState, ProviderCallbackParams, ProviderEmailNotVerifiedError, ProviderProfile, ProviderUserCreator, ProvidersConfig, RefreshToken, RefreshTokenConfig, RefreshTokenIssueOptions, RequestUser, RequestUserFailure, RequestUserOutcome, ResolveRequestUserOptions, SessionMiddlewareOptions, SessionResolverOptions, SessionSource, SetAuthCookieOptions, TokenFrom, TokenPair, TokenType, TokenTypeError, XProvider, XProviderConfig, accessTokenSchema, assertCsrfOriginAllowed, authDefaultTranslations, authEvents, authMiddleware, authMigrations, authService, authTokenFamilySchema, completeProviderLogin, consumeOneTimeToken, currentUser, generateJWTSecret, generatePasskeyAuthenticationOptions, generatePasskeyRegistrationOptions, hashOneTimeToken, isEmailVerified, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, issueOneTimeToken, jwt, loginThrottleMiddleware, oneTimeTokenModel, oneTimeTokenSchema, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, pageSession, passkeyCredentialSchema, pkceChallenge, providerAccountSchema, readCredential, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerAuthTranslations, registerJWTSecretGeneratorCommand, requestOtp, requestPasswordReset, requireVerifiedEmail, requiresCsrfOriginCheck, resetPassword, resolveAuthProvider, resolveRequestUser, resolveRequestUserOutcome, safeEqual, sendEmailVerification, sessionMiddleware, startProviderLogin, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
|
|
57
|
+
export { APPLE_AUTH_FEATURE, AUTH_MIDDLEWARE_DESCRIPTOR, AccessToken, AccessTokenConfig, AccessTokenOutput, AppleJoseModule, AppleProvider, AppleProviderConfig, Auth, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthErrorCodes, AuthErrorTranslations, AuthEventCallback, AuthEventName, AuthEventPayloads, AuthMiddlewareDescriptor, AuthMiddlewareDescriptorSource, AuthMiddlewareOptions, AuthNotification, AuthProvider, AuthProviderSdkMissingError, AuthRefreshCredentialDescriptor, AuthTokenFamily, AuthTranslations, Authenticable, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CookieDomainRequestRequiredError, CookieDomainResolver, CsrfConfig, CsrfOriginMismatchError, CsrfOriginMismatchReason, DeviceInfo, DiscordProvider, DiscordProviderConfig, EmailNotVerifiedError, EmailVerificationConfig, FacebookProvider, FacebookProviderConfig, GOOGLE_AUTH_FEATURE, GitHubProvider, GitHubProviderConfig, GoogleProvider, GoogleProviderConfig, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, IssuedOneTimeToken, JoseModule, LINKEDIN_AUTH_FEATURE, LegacyJwtConfig, LinkedInProvider, LinkedInProviderConfig, LoginResult, LoginThrottleIdentifier, LoginThrottleOptions, LogoutWithoutTokenBehavior, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenPurpose, OneTimeTokenUrlBuilder, OtpConfig, OtpMessage, OtpSender, PageAuthConfig, PageSessionOptions, PageSessionResolver, PasskeyCredential, type PasskeyOptionsJSON, type PasskeyResponseJSON, PasskeysConfig, PasswordResetConfig, PasswordSetter, ProviderAccount, ProviderAuthorizationState, ProviderCallbackParams, ProviderEmailNotVerifiedError, ProviderProfile, ProviderUserCreator, ProvidersConfig, RefreshToken, RefreshTokenConfig, RefreshTokenIssueOptions, RequestUser, RequestUserFailure, RequestUserOutcome, ResolveRequestUserOptions, SessionMiddlewareOptions, SessionResolverOptions, SessionSource, SetAuthCookieOptions, TokenFrom, TokenPair, TokenType, TokenTypeError, XProvider, XProviderConfig, accessTokenSchema, assertCsrfOriginAllowed, authDefaultTranslations, authEvents, authMiddleware, authMigrations, authService, authTokenFamilySchema, completeProviderLogin, consumeOneTimeToken, currentUser, generateJWTSecret, generatePasskeyAuthenticationOptions, generatePasskeyRegistrationOptions, hashOneTimeToken, isEmailVerified, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, issueOneTimeToken, jwt, loginThrottleMiddleware, oneTimeTokenModel, oneTimeTokenSchema, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, pageSession, passkeyCredentialSchema, pkceChallenge, providerAccountSchema, readCredential, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerAuthTranslations, registerJWTSecretGeneratorCommand, requestOtp, requestPasswordReset, requireVerifiedEmail, requiresCsrfOriginCheck, resetPassword, resolveAuthProvider, resolveRequestUser, resolveRequestUserOutcome, safeEqual, sendEmailVerification, sessionMiddleware, startProviderLogin, throttleIdentifierHash, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
|
|
58
58
|
import "./contracts/request-locals.mjs";
|
package/esm/index.mjs
CHANGED
|
@@ -27,7 +27,7 @@ import "./errors/index.mjs";
|
|
|
27
27
|
import "./localization/index.mjs";
|
|
28
28
|
import { readCredential, resolveRequestUser, resolveRequestUserOutcome } from "./services/resolve-request-user.mjs";
|
|
29
29
|
import { AUTH_MIDDLEWARE_DESCRIPTOR, authMiddleware } from "./middleware/auth.middleware.mjs";
|
|
30
|
-
import { loginThrottleMiddleware } from "./middleware/login-throttle.middleware.mjs";
|
|
30
|
+
import { loginThrottleMiddleware, throttleIdentifierHash } from "./middleware/login-throttle.middleware.mjs";
|
|
31
31
|
import { tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware } from "./middleware/one-time-token-throttle.middleware.mjs";
|
|
32
32
|
import { otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware } from "./middleware/otp-throttle.middleware.mjs";
|
|
33
33
|
import { requireVerifiedEmail } from "./middleware/require-verified-email.middleware.mjs";
|
|
@@ -60,4 +60,4 @@ import { pageSession, sessionMiddleware } from "./session/page-session.mjs";
|
|
|
60
60
|
import "./session/index.mjs";
|
|
61
61
|
import "./utils/index.mjs";
|
|
62
62
|
|
|
63
|
-
export { APPLE_AUTH_FEATURE, AUTH_MIDDLEWARE_DESCRIPTOR, AccessToken, AppleProvider, Auth, AuthErrorCodes, AuthProviderSdkMissingError, AuthTokenFamily, CookieDomainRequestRequiredError, CsrfOriginMismatchError, DiscordProvider, EmailNotVerifiedError, FacebookProvider, GOOGLE_AUTH_FEATURE, GitHubProvider, GoogleProvider, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, LINKEDIN_AUTH_FEATURE, LinkedInProvider, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, PasskeyCredential, ProviderAccount, ProviderEmailNotVerifiedError, RefreshToken, TokenTypeError, XProvider, accessTokenSchema, assertCsrfOriginAllowed, authDefaultTranslations, authEvents, authMiddleware, authMigrations, authService, authTokenFamilySchema, completeProviderLogin, consumeOneTimeToken, currentUser, generateJWTSecret, generatePasskeyAuthenticationOptions, generatePasskeyRegistrationOptions, hashOneTimeToken, isEmailVerified, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, issueOneTimeToken, jwt, loginThrottleMiddleware, oneTimeTokenModel, oneTimeTokenSchema, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, pageSession, passkeyCredentialSchema, pkceChallenge, providerAccountSchema, readCredential, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerAuthTranslations, registerJWTSecretGeneratorCommand, requestOtp, requestPasswordReset, requireVerifiedEmail, requiresCsrfOriginCheck, resetPassword, resolveAuthProvider, resolveRequestUser, resolveRequestUserOutcome, safeEqual, sendEmailVerification, sessionMiddleware, startProviderLogin, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
|
|
63
|
+
export { APPLE_AUTH_FEATURE, AUTH_MIDDLEWARE_DESCRIPTOR, AccessToken, AppleProvider, Auth, AuthErrorCodes, AuthProviderSdkMissingError, AuthTokenFamily, CookieDomainRequestRequiredError, CsrfOriginMismatchError, DiscordProvider, EmailNotVerifiedError, FacebookProvider, GOOGLE_AUTH_FEATURE, GitHubProvider, GoogleProvider, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, LINKEDIN_AUTH_FEATURE, LinkedInProvider, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, PasskeyCredential, ProviderAccount, ProviderEmailNotVerifiedError, RefreshToken, TokenTypeError, XProvider, accessTokenSchema, assertCsrfOriginAllowed, authDefaultTranslations, authEvents, authMiddleware, authMigrations, authService, authTokenFamilySchema, completeProviderLogin, consumeOneTimeToken, currentUser, generateJWTSecret, generatePasskeyAuthenticationOptions, generatePasskeyRegistrationOptions, hashOneTimeToken, isEmailVerified, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, issueOneTimeToken, jwt, loginThrottleMiddleware, oneTimeTokenModel, oneTimeTokenSchema, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, pageSession, passkeyCredentialSchema, pkceChallenge, providerAccountSchema, readCredential, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerAuthTranslations, registerJWTSecretGeneratorCommand, requestOtp, requestPasswordReset, requireVerifiedEmail, requiresCsrfOriginCheck, resetPassword, resolveAuthProvider, resolveRequestUser, resolveRequestUserOutcome, safeEqual, sendEmailVerification, sessionMiddleware, startProviderLogin, throttleIdentifierHash, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../../packages/auth/src/localization/ar.translations.ts
|
|
2
2
|
/**
|
|
3
3
|
* Built-in Arabic text for auth's translation keys. Applications override
|
|
4
4
|
* any of these by registering the same key in their own locale files.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ar.translations.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"ar.translations.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/localization/ar.translations.ts"],"sourcesContent":["import type { AuthTranslations } from \"./auth-translations.type\";\n\n/**\n * Built-in Arabic text for auth's translation keys. Applications override\n * any of these by registering the same key in their own locale files.\n */\nexport const authArabicTranslations: AuthTranslations = {\n auth: {\n errors: {\n missingAccessToken: \"رمز الوصول مفقود.\",\n invalidAccessToken: \"رمز الوصول غير صالح أو منتهي الصلاحية.\",\n unauthorized: \"غير مصرح لك بالوصول إلى هذا المورد.\",\n csrfOriginMismatch: \"مصدر الطلب غير مسموح به.\",\n tooManyAttempts: \"محاولات كثيرة جدًا. يرجى المحاولة مرة أخرى لاحقًا.\",\n },\n },\n};\n"],"mappings":";;;;;AAMA,MAAa,yBAA2C,EACtD,MAAM,EACJ,QAAQ;CACN,oBAAoB;CACpB,oBAAoB;CACpB,cAAc;CACd,oBAAoB;CACpB,iBAAiB;AACnB,EACF,EACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthTranslations } from "./auth-translations.type.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/auth/src/localization/auth-default-translations.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Every locale auth ships default text for, keyed by locale code.
|
|
6
6
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-default-translations.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"auth-default-translations.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/localization/auth-default-translations.ts"],"mappings":";;;;;AAOA;cAAa,uBAAA,EAAyB,MAAM,SAAS,gBAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { authArabicTranslations } from "./ar.translations.mjs";
|
|
2
2
|
import { authEnglishTranslations } from "./en.translations.mjs";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/localization/auth-default-translations.ts
|
|
5
5
|
/**
|
|
6
6
|
* Every locale auth ships default text for, keyed by locale code.
|
|
7
7
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-default-translations.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"auth-default-translations.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/localization/auth-default-translations.ts"],"sourcesContent":["import { authArabicTranslations } from \"./ar.translations\";\nimport type { AuthTranslations } from \"./auth-translations.type\";\nimport { authEnglishTranslations } from \"./en.translations\";\n\n/**\n * Every locale auth ships default text for, keyed by locale code.\n */\nexport const authDefaultTranslations: Record<string, AuthTranslations> = {\n en: authEnglishTranslations,\n ar: authArabicTranslations,\n};\n"],"mappings":";;;;;;;AAOA,MAAa,0BAA4D;CACvE,IAAI;CACJ,IAAI;AACN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-translations.type.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"auth-translations.type.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/localization/auth-translations.type.ts"],"mappings":";;AAOA;;;;;;KAAY,qBAAA;EAQV,oCANA,kBAAA,UAQe;EANf,kBAAA,UAaU;EAXV,YAAA;EAEA,kBAAA,UAUA;EARA,eAAA;AAAA;;AAS+B;;;KAFrB,gBAAA;EACV,IAAA;IACE,MAAA,EAAQ,qBAAqB;EAAA;AAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../../packages/auth/src/localization/en.translations.ts
|
|
2
2
|
/**
|
|
3
3
|
* Built-in English text for auth's translation keys. Applications override
|
|
4
4
|
* any of these by registering the same key in their own locale files.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.translations.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"en.translations.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/localization/en.translations.ts"],"sourcesContent":["import type { AuthTranslations } from \"./auth-translations.type\";\n\n/**\n * Built-in English text for auth's translation keys. Applications override\n * any of these by registering the same key in their own locale files.\n */\nexport const authEnglishTranslations: AuthTranslations = {\n auth: {\n errors: {\n missingAccessToken: \"Missing access token.\",\n invalidAccessToken: \"Invalid or expired access token.\",\n unauthorized: \"You are not authorized to access this resource.\",\n csrfOriginMismatch: \"Request origin is not allowed.\",\n tooManyAttempts: \"Too many attempts. Please try again later.\",\n },\n },\n};\n"],"mappings":";;;;;AAMA,MAAa,0BAA4C,EACvD,MAAM,EACJ,QAAQ;CACN,oBAAoB;CACpB,oBAAoB;CACpB,cAAc;CACd,oBAAoB;CACpB,iBAAiB;AACnB,EACF,EACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"init.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/localization/init.ts"],"sourcesContent":["import { registerAuthTranslations } from \"./register-auth-translations\";\n\n// Side-effect module: importing `@warlock.js/auth` registers its default\n// translations, so `t(\"auth.errors.*\")` never returns the raw key.\nregisterAuthTranslations();\n"],"mappings":";;;AAIA,yBAAyB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../../packages/auth/src/localization/register-auth-translations.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* Register auth's built-in translations with `@mongez/localization`, the
|
|
4
4
|
* same store `t()` from `@warlock.js/core` reads.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-auth-translations.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"register-auth-translations.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/localization/register-auth-translations.ts"],"mappings":";;AAgBA;;;;AAAwC;;;;;;;iBAAxB,wBAAA"}
|
|
@@ -2,7 +2,7 @@ import { authDefaultTranslations } from "./auth-default-translations.mjs";
|
|
|
2
2
|
import { flatten, get, set } from "@mongez/reinforcements";
|
|
3
3
|
import { extend, getKeywordsListOf } from "@warlock.js/core";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/auth/src/localization/register-auth-translations.ts
|
|
6
6
|
/**
|
|
7
7
|
* Register auth's built-in translations with `@mongez/localization`, the
|
|
8
8
|
* same store `t()` from `@warlock.js/core` reads.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-auth-translations.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"register-auth-translations.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/localization/register-auth-translations.ts"],"sourcesContent":["import { flatten, get, set } from \"@mongez/reinforcements\";\nimport { extend, getKeywordsListOf, type Keywords } from \"@warlock.js/core\";\nimport { authDefaultTranslations } from \"./auth-default-translations\";\n\n/**\n * Register auth's built-in translations with `@mongez/localization`, the\n * same store `t()` from `@warlock.js/core` reads.\n *\n * Only keys the application has NOT already translated are added, so an app\n * translation wins regardless of load order: registered before this runs, it\n * is left untouched; registered after, `extend()` merges it over the default.\n * An app locale file that predates a newer auth key therefore gets the\n * default text for that key instead of the raw key string.\n *\n * Runs once at import of `@warlock.js/auth`; safe to call again.\n */\nexport function registerAuthTranslations(): void {\n for (const [localeCode, translations] of Object.entries(authDefaultTranslations)) {\n const existing = getKeywordsListOf(localeCode) ?? {};\n const missing: Keywords = {};\n\n for (const [key, text] of Object.entries(flatten(translations))) {\n if (get(existing, key) === undefined) {\n set(missing, key, text);\n }\n }\n\n if (Object.keys(missing).length > 0) {\n extend(localeCode, missing);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,SAAgB,2BAAiC;CAC/C,KAAK,MAAM,CAAC,YAAY,iBAAiB,OAAO,QAAQ,uBAAuB,GAAG;EAChF,MAAM,WAAW,kBAAkB,UAAU,KAAK,CAAC;EACnD,MAAM,UAAoB,CAAC;EAE3B,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,QAAQ,YAAY,CAAC,GAC5D,IAAI,IAAI,UAAU,GAAG,MAAM,QACzB,IAAI,SAAS,KAAK,IAAI;EAI1B,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAChC,OAAO,YAAY,OAAO;CAE9B;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AuthMiddlewareOptions, TokenFrom } from "../contracts/types.mjs";
|
|
2
2
|
import { Middleware } from "@warlock.js/core";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/middleware/auth.middleware.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Well-known symbol under which `authMiddleware()` tags the middleware it
|
|
7
7
|
* returns. Shared via the global symbol registry so other packages can read the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/auth.middleware.ts"],"mappings":";;;;;;AAsBA;;;cAAa,0BAAA;AAAuD;AAAA,KAGxD,8BAAA;EAA8C,MAAM;AAAA;;AAAA;AAMhE;;KAAY,wBAAA;EAE6B,6EAAvC,OAAA,EAAS,8BAA8B,IAA9B;EAET,SAAA;AAAA;AAAS;AAoGX;;;;AAA4C;AAC5C;;;;;;;;AAA0E;AAC1E;;;;;;AAtGW,iBAoGK,cAAA,IAAkB,UAAU;AAAA,iBAC5B,cAAA,CAAe,OAAA,EAAS,qBAAA,GAAwB,UAAU;AAAA,iBAC1D,cAAA,CACd,eAAA,qBACA,OAAA,GAAU,qBAAA,GACT,UAAU;AAAA,iBACG,cAAA,CACd,eAAA,qBACA,SAAA,GAAY,SAAA,GACX,UAAU"}
|
|
@@ -6,7 +6,7 @@ import { localizedLoginPath } from "./localized-login-path.mjs";
|
|
|
6
6
|
import { config, t } from "@warlock.js/core";
|
|
7
7
|
import { log } from "@warlock.js/logger";
|
|
8
8
|
|
|
9
|
-
//#region
|
|
9
|
+
//#region ../../packages/auth/src/middleware/auth.middleware.ts
|
|
10
10
|
/**
|
|
11
11
|
* Well-known symbol under which `authMiddleware()` tags the middleware it
|
|
12
12
|
* returns. Shared via the global symbol registry so other packages can read the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.middleware.mjs","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"sourcesContent":["import { config, type HttpContext, t, type Middleware } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport type {\n AuthCredentialDescriptor,\n AuthMiddlewareOptions,\n TokenFrom,\n} from \"../contracts/types\";\nimport { authConfig } from \"../services/auth-config\";\nimport { readCredential, resolveRequestUserOutcome } from \"../services/resolve-request-user\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\nimport {\n assertCsrfOriginAllowed,\n CsrfOriginMismatchError,\n requiresCsrfOriginCheck,\n} from \"./csrf-origin-check\";\nimport { localizedLoginPath } from \"./localized-login-path\";\n\n/**\n * Well-known symbol under which `authMiddleware()` tags the middleware it\n * returns. Shared via the global symbol registry so other packages can read the\n * descriptor structurally (`Symbol.for(\"warlock.auth\")`) without importing auth.\n */\nexport const AUTH_MIDDLEWARE_DESCRIPTOR = Symbol.for(\"warlock.auth\");\n\n/** A credential source an auth middleware reads: the Authorization header or a named cookie. */\nexport type AuthMiddlewareDescriptorSource = \"header\" | { cookie: string };\n\n/**\n * Frozen, JSON-safe description of how an `authMiddleware()` instance\n * authenticates, stored (non-enumerably) under {@link AUTH_MIDDLEWARE_DESCRIPTOR}.\n */\nexport type AuthMiddlewareDescriptor = {\n /** Resolved credential sources; with dual sources the header comes first. */\n sources: AuthMiddlewareDescriptorSource[];\n /** Resolved allowed user types; an empty array means any authenticated user. */\n userTypes: string[];\n};\n\n/** The 401 body shape every rejection in this middleware carries. */\ntype UnauthorizedPayload = { error: string; errorCode: string };\n\n/**\n * Reject an unauthenticated request, choosing the representation by ROUTE KIND.\n *\n * An API route always gets the JSON 401 it has always gotten. A PAGE route\n * (`request.route.isPage` — the React-SSR pages) instead redirects a logged-out\n * browser to the configured login path with a `returnUrl`, so a human\n * navigating to a guarded page lands on the login screen rather than reading a\n * raw JSON error blob — the defect in finding b9ab9804.\n *\n * The redirect is OPT-IN and backward-compatible: with no `auth.pageAuth.loginPath`\n * configured, even a page route falls back to the JSON 401, so an app that has\n * not asked for page-login UX keeps exactly its current behavior. The API 401\n * contract is never changed for API routes.\n *\n * `returnUrl` carries `request.url` — the relative path + query the browser was\n * on — never the absolute URL: a relative target cannot be turned into an\n * open-redirect off this app's origin.\n */\nfunction rejectUnauthorized(\n { request, response }: HttpContext,\n payload: UnauthorizedPayload,\n redirect?: AuthMiddlewareOptions[\"redirect\"],\n) {\n const loginPath = redirect?.to ?? authConfig.pageAuth.loginPath();\n const returnUrlParam = redirect?.returnUrlParam ?? authConfig.pageAuth.returnUrlParam();\n\n if (loginPath && request.route?.isPage) {\n // Under an active `web.localeRouting` strategy, the login destination\n // itself must carry the request's locale prefix — otherwise an anonymous\n // `/ar/admin` bounces to the DEFAULT locale's `/login` rather than\n // `/ar/login`, even though `returnUrl` below correctly points back at\n // `/ar/admin`. See `./localized-login-path.ts`.\n const destination = localizedLoginPath(loginPath, request.locale);\n const separator = destination.includes(\"?\") ? \"&\" : \"?\";\n const returnUrl = encodeURIComponent(request.url);\n\n return response.redirect(`${destination}${separator}${returnUrlParam}=${returnUrl}`);\n }\n\n return response.unauthorized(payload);\n}\n\nfunction tokenFromDescriptor(descriptor: Partial<AuthCredentialDescriptor>): TokenFrom {\n if (!descriptor.source || descriptor.source === \"header\") return \"header\";\n\n if (!descriptor.key) {\n throw new Error(\"authMiddleware cookie credentials require a cookie key.\");\n }\n\n return `cookie:${descriptor.key}`;\n}\n\nfunction descriptorSource(tokenFrom: TokenFrom): AuthMiddlewareDescriptorSource {\n return tokenFrom === \"header\" ? \"header\" : { cookie: tokenFrom.slice(\"cookie:\".length) };\n}\n\nfunction resolveAllowedTypes(value: string | string[] | undefined): string[] {\n if (value !== undefined) return Array.isArray(value) ? value : [value];\n\n const configuredDefault = config.key(\"auth.defaultUserType\") as string | undefined;\n\n if (configuredDefault) return [configuredDefault];\n\n const userTypes = config.key(\"auth.userType\", {}) as Record<string, unknown>;\n const configuredTypes = Object.keys(userTypes);\n\n if (configuredTypes.length === 1) return configuredTypes;\n\n throw new Error(\n \"authMiddleware requires a user type when auth.defaultUserType is unset and multiple user types exist.\",\n );\n}\n\n/**\n * Build a route gate. Authentication is required unless optional is explicit.\n *\n * An omitted user type selects the configured default; an explicit argument\n * selects which user types may pass:\n * - `[]` — any authenticated user (token required, type not checked).\n * - `\"admin\"` / `[\"admin\", \"staff\"]` — token required AND the user's\n * `userType` must be one of the listed types.\n *\n * `{ optional: true }` allows anonymous requests. All other forms reject\n * missing or invalid credentials with 401, or a configured page redirect.\n *\n * `tokenFrom` selects one credential source and defaults to `\"header\"`.\n * A single source avoids making credential precedence depend on array order.\n *\n * @example\n * router.get(\"/account\", authMiddleware([]), accountController);\n * router.get(\"/admin\", authMiddleware(\"admin\"), adminController);\n * router.get(\"/back-office\", authMiddleware([\"admin\", \"staff\"]), backOfficeController);\n * router.get(\"/browser-account\", authMiddleware([], \"cookie:token\"), accountController);\n */\nexport function authMiddleware(): Middleware;\nexport function authMiddleware(options: AuthMiddlewareOptions): Middleware;\nexport function authMiddleware(\n allowedUserType: string | string[],\n options?: AuthMiddlewareOptions,\n): Middleware;\nexport function authMiddleware(\n allowedUserType: string | string[],\n tokenFrom?: TokenFrom,\n): Middleware;\nexport function authMiddleware(\n userTypeOrOptions?: string | string[] | AuthMiddlewareOptions,\n legacyTokenFromOrOptions?: TokenFrom | AuthMiddlewareOptions,\n): Middleware {\n const options =\n typeof userTypeOrOptions === \"object\" && !Array.isArray(userTypeOrOptions)\n ? userTypeOrOptions\n : typeof legacyTokenFromOrOptions === \"object\"\n ? legacyTokenFromOrOptions\n : undefined;\n const selectedUserType =\n typeof userTypeOrOptions === \"string\" || Array.isArray(userTypeOrOptions)\n ? userTypeOrOptions\n : undefined;\n const allowedTypes = resolveAllowedTypes(selectedUserType);\n const legacyTokenFrom =\n typeof legacyTokenFromOrOptions === \"string\" ? legacyTokenFromOrOptions : undefined;\n const dualSources = options?.sources?.length ? options.sources.map(tokenFromDescriptor) : undefined;\n\n if (dualSources && options?.source) {\n throw new Error(\"authMiddleware accepts either `source` or `sources`, not both.\");\n }\n\n const dualHeader = dualSources?.includes(\"header\") ?? false;\n const dualCookies = dualSources?.filter(source => source !== \"header\") ?? [];\n\n if (dualSources && (dualCookies.length > 1 || dualSources.length - dualCookies.length > 1)) {\n throw new Error(\"authMiddleware `sources` accepts at most one header and one cookie.\");\n }\n\n // With dual sources the configured cookie is the base credential; the header\n // is chosen per request, ahead of it.\n const cookieTokenFrom: TokenFrom = dualSources\n ? (dualCookies[0] ?? \"header\")\n : options\n ? tokenFromDescriptor(options)\n : (legacyTokenFrom ?? \"header\");\n const tokenFrom = cookieTokenFrom;\n const refreshCredential = options?.refresh ? tokenFromDescriptor(options.refresh) : undefined;\n const renewalUserType = allowedTypes.length === 1 ? allowedTypes[0] : undefined;\n\n if (\n refreshCredential &&\n (tokenFrom === \"header\" || refreshCredential === \"header\" || !renewalUserType)\n ) {\n throw new Error(\n \"authMiddleware automatic renewal requires one allowed user type and cookie access and refresh credentials.\",\n );\n }\n\n const optional = options?.optional === true;\n\n const auth: Middleware = async ({ request, response }) => {\n const reject = (payload: UnauthorizedPayload) =>\n rejectUnauthorized({ request, response }, payload, options?.redirect);\n\n // A present Authorization header always wins and never falls back to the\n // cookie: the credential used is the header, so no renewal and no CSRF check.\n const headerWins = dualHeader && Boolean(request.authorizationValue);\n const usedTokenFrom: TokenFrom = headerWins ? \"header\" : tokenFrom;\n const usedRefresh = headerWins ? undefined : refreshCredential;\n\n // Renewal can mint cookies, so a cookie-authenticated unsafe request must\n // pass the same CSRF check even when its access cookie is missing.\n if (\n (readCredential(request, usedTokenFrom) ||\n (usedRefresh && readCredential(request, usedRefresh))) &&\n requiresCsrfOriginCheck(usedTokenFrom, request.method)\n ) {\n try {\n assertCsrfOriginAllowed(request);\n } catch (error) {\n if (!(error instanceof CsrfOriginMismatchError)) throw error;\n\n log.error(\"http\", \"auth\", error);\n return response.forbidden({\n error: t(\"auth.errors.csrfOriginMismatch\"),\n errorCode: AuthErrorCodes.CsrfOriginMismatch,\n });\n }\n }\n\n const outcome = await resolveRequestUserOutcome(request, response, {\n tokenFrom: usedTokenFrom,\n refreshCredential: usedRefresh,\n overlapMs: options?.refresh?.overlapMs,\n allowedTypes,\n renewalUserType,\n });\n\n if (outcome.user) {\n request.locals.user = outcome.user;\n return;\n }\n\n if (outcome.failure === \"forbidden\") {\n return response.forbidden({\n error: t(\"auth.errors.unauthorized\"),\n errorCode: AuthErrorCodes.Unauthorized,\n });\n }\n\n if (optional) return;\n\n if (outcome.failure === \"missing\") {\n return reject({\n error: t(\"auth.errors.missingAccessToken\"),\n errorCode: AuthErrorCodes.MissingAccessToken,\n });\n }\n\n if (outcome.failure === \"unauthorized\") {\n return reject({\n error: t(\"auth.errors.unauthorized\"),\n errorCode: AuthErrorCodes.Unauthorized,\n });\n }\n\n return reject({\n error: t(\"auth.errors.invalidAccessToken\"),\n errorCode: AuthErrorCodes.InvalidAccessToken,\n });\n };\n\n const descriptorSources: AuthMiddlewareDescriptorSource[] = dualSources\n ? [...(dualHeader ? ([\"header\"] as const) : []), ...dualCookies.map(descriptorSource)]\n : [descriptorSource(tokenFrom)];\n const descriptor: AuthMiddlewareDescriptor = Object.freeze({\n sources: Object.freeze(descriptorSources.map(source =>\n typeof source === \"string\" ? source : Object.freeze({ ...source }),\n )) as AuthMiddlewareDescriptorSource[],\n userTypes: Object.freeze([...allowedTypes]) as string[],\n });\n\n // Non-enumerable so the tag never shows up in logs, spreads or JSON.\n Object.defineProperty(auth, AUTH_MIDDLEWARE_DESCRIPTOR, { value: descriptor, enumerable: false });\n\n return auth;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.middleware.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/auth.middleware.ts"],"sourcesContent":["import { config, type HttpContext, t, type Middleware } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport type {\n AuthCredentialDescriptor,\n AuthMiddlewareOptions,\n TokenFrom,\n} from \"../contracts/types\";\nimport { authConfig } from \"../services/auth-config\";\nimport { readCredential, resolveRequestUserOutcome } from \"../services/resolve-request-user\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\nimport {\n assertCsrfOriginAllowed,\n CsrfOriginMismatchError,\n requiresCsrfOriginCheck,\n} from \"./csrf-origin-check\";\nimport { localizedLoginPath } from \"./localized-login-path\";\n\n/**\n * Well-known symbol under which `authMiddleware()` tags the middleware it\n * returns. Shared via the global symbol registry so other packages can read the\n * descriptor structurally (`Symbol.for(\"warlock.auth\")`) without importing auth.\n */\nexport const AUTH_MIDDLEWARE_DESCRIPTOR = Symbol.for(\"warlock.auth\");\n\n/** A credential source an auth middleware reads: the Authorization header or a named cookie. */\nexport type AuthMiddlewareDescriptorSource = \"header\" | { cookie: string };\n\n/**\n * Frozen, JSON-safe description of how an `authMiddleware()` instance\n * authenticates, stored (non-enumerably) under {@link AUTH_MIDDLEWARE_DESCRIPTOR}.\n */\nexport type AuthMiddlewareDescriptor = {\n /** Resolved credential sources; with dual sources the header comes first. */\n sources: AuthMiddlewareDescriptorSource[];\n /** Resolved allowed user types; an empty array means any authenticated user. */\n userTypes: string[];\n};\n\n/** The 401 body shape every rejection in this middleware carries. */\ntype UnauthorizedPayload = { error: string; errorCode: string };\n\n/**\n * Reject an unauthenticated request, choosing the representation by ROUTE KIND.\n *\n * An API route always gets the JSON 401 it has always gotten. A PAGE route\n * (`request.route.isPage` — the React-SSR pages) instead redirects a logged-out\n * browser to the configured login path with a `returnUrl`, so a human\n * navigating to a guarded page lands on the login screen rather than reading a\n * raw JSON error blob — the defect in finding b9ab9804.\n *\n * The redirect is OPT-IN and backward-compatible: with no `auth.pageAuth.loginPath`\n * configured, even a page route falls back to the JSON 401, so an app that has\n * not asked for page-login UX keeps exactly its current behavior. The API 401\n * contract is never changed for API routes.\n *\n * `returnUrl` carries `request.url` — the relative path + query the browser was\n * on — never the absolute URL: a relative target cannot be turned into an\n * open-redirect off this app's origin.\n */\nfunction rejectUnauthorized(\n { request, response }: HttpContext,\n payload: UnauthorizedPayload,\n redirect?: AuthMiddlewareOptions[\"redirect\"],\n) {\n const loginPath = redirect?.to ?? authConfig.pageAuth.loginPath();\n const returnUrlParam = redirect?.returnUrlParam ?? authConfig.pageAuth.returnUrlParam();\n\n if (loginPath && request.route?.isPage) {\n // Under an active `web.localeRouting` strategy, the login destination\n // itself must carry the request's locale prefix — otherwise an anonymous\n // `/ar/admin` bounces to the DEFAULT locale's `/login` rather than\n // `/ar/login`, even though `returnUrl` below correctly points back at\n // `/ar/admin`. See `./localized-login-path.ts`.\n const destination = localizedLoginPath(loginPath, request.locale);\n const separator = destination.includes(\"?\") ? \"&\" : \"?\";\n const returnUrl = encodeURIComponent(request.url);\n\n return response.redirect(`${destination}${separator}${returnUrlParam}=${returnUrl}`);\n }\n\n return response.unauthorized(payload);\n}\n\nfunction tokenFromDescriptor(descriptor: Partial<AuthCredentialDescriptor>): TokenFrom {\n if (!descriptor.source || descriptor.source === \"header\") return \"header\";\n\n if (!descriptor.key) {\n throw new Error(\"authMiddleware cookie credentials require a cookie key.\");\n }\n\n return `cookie:${descriptor.key}`;\n}\n\nfunction descriptorSource(tokenFrom: TokenFrom): AuthMiddlewareDescriptorSource {\n return tokenFrom === \"header\" ? \"header\" : { cookie: tokenFrom.slice(\"cookie:\".length) };\n}\n\nfunction resolveAllowedTypes(value: string | string[] | undefined): string[] {\n if (value !== undefined) return Array.isArray(value) ? value : [value];\n\n const configuredDefault = config.key(\"auth.defaultUserType\") as string | undefined;\n\n if (configuredDefault) return [configuredDefault];\n\n const userTypes = config.key(\"auth.userType\", {}) as Record<string, unknown>;\n const configuredTypes = Object.keys(userTypes);\n\n if (configuredTypes.length === 1) return configuredTypes;\n\n throw new Error(\n \"authMiddleware requires a user type when auth.defaultUserType is unset and multiple user types exist.\",\n );\n}\n\n/**\n * Build a route gate. Authentication is required unless optional is explicit.\n *\n * An omitted user type selects the configured default; an explicit argument\n * selects which user types may pass:\n * - `[]` — any authenticated user (token required, type not checked).\n * - `\"admin\"` / `[\"admin\", \"staff\"]` — token required AND the user's\n * `userType` must be one of the listed types.\n *\n * `{ optional: true }` allows anonymous requests. All other forms reject\n * missing or invalid credentials with 401, or a configured page redirect.\n *\n * `tokenFrom` selects one credential source and defaults to `\"header\"`.\n * A single source avoids making credential precedence depend on array order.\n *\n * @example\n * router.get(\"/account\", authMiddleware([]), accountController);\n * router.get(\"/admin\", authMiddleware(\"admin\"), adminController);\n * router.get(\"/back-office\", authMiddleware([\"admin\", \"staff\"]), backOfficeController);\n * router.get(\"/browser-account\", authMiddleware([], \"cookie:token\"), accountController);\n */\nexport function authMiddleware(): Middleware;\nexport function authMiddleware(options: AuthMiddlewareOptions): Middleware;\nexport function authMiddleware(\n allowedUserType: string | string[],\n options?: AuthMiddlewareOptions,\n): Middleware;\nexport function authMiddleware(\n allowedUserType: string | string[],\n tokenFrom?: TokenFrom,\n): Middleware;\nexport function authMiddleware(\n userTypeOrOptions?: string | string[] | AuthMiddlewareOptions,\n legacyTokenFromOrOptions?: TokenFrom | AuthMiddlewareOptions,\n): Middleware {\n const options =\n typeof userTypeOrOptions === \"object\" && !Array.isArray(userTypeOrOptions)\n ? userTypeOrOptions\n : typeof legacyTokenFromOrOptions === \"object\"\n ? legacyTokenFromOrOptions\n : undefined;\n const selectedUserType =\n typeof userTypeOrOptions === \"string\" || Array.isArray(userTypeOrOptions)\n ? userTypeOrOptions\n : undefined;\n const allowedTypes = resolveAllowedTypes(selectedUserType);\n const legacyTokenFrom =\n typeof legacyTokenFromOrOptions === \"string\" ? legacyTokenFromOrOptions : undefined;\n const dualSources = options?.sources?.length ? options.sources.map(tokenFromDescriptor) : undefined;\n\n if (dualSources && options?.source) {\n throw new Error(\"authMiddleware accepts either `source` or `sources`, not both.\");\n }\n\n const dualHeader = dualSources?.includes(\"header\") ?? false;\n const dualCookies = dualSources?.filter(source => source !== \"header\") ?? [];\n\n if (dualSources && (dualCookies.length > 1 || dualSources.length - dualCookies.length > 1)) {\n throw new Error(\"authMiddleware `sources` accepts at most one header and one cookie.\");\n }\n\n // With dual sources the configured cookie is the base credential; the header\n // is chosen per request, ahead of it.\n const cookieTokenFrom: TokenFrom = dualSources\n ? (dualCookies[0] ?? \"header\")\n : options\n ? tokenFromDescriptor(options)\n : (legacyTokenFrom ?? \"header\");\n const tokenFrom = cookieTokenFrom;\n const refreshCredential = options?.refresh ? tokenFromDescriptor(options.refresh) : undefined;\n const renewalUserType = allowedTypes.length === 1 ? allowedTypes[0] : undefined;\n\n if (\n refreshCredential &&\n (tokenFrom === \"header\" || refreshCredential === \"header\" || !renewalUserType)\n ) {\n throw new Error(\n \"authMiddleware automatic renewal requires one allowed user type and cookie access and refresh credentials.\",\n );\n }\n\n const optional = options?.optional === true;\n\n const auth: Middleware = async ({ request, response }) => {\n const reject = (payload: UnauthorizedPayload) =>\n rejectUnauthorized({ request, response }, payload, options?.redirect);\n\n // A present Authorization header always wins and never falls back to the\n // cookie: the credential used is the header, so no renewal and no CSRF check.\n const headerWins = dualHeader && Boolean(request.authorizationValue);\n const usedTokenFrom: TokenFrom = headerWins ? \"header\" : tokenFrom;\n const usedRefresh = headerWins ? undefined : refreshCredential;\n\n // Renewal can mint cookies, so a cookie-authenticated unsafe request must\n // pass the same CSRF check even when its access cookie is missing.\n if (\n (readCredential(request, usedTokenFrom) ||\n (usedRefresh && readCredential(request, usedRefresh))) &&\n requiresCsrfOriginCheck(usedTokenFrom, request.method)\n ) {\n try {\n assertCsrfOriginAllowed(request);\n } catch (error) {\n if (!(error instanceof CsrfOriginMismatchError)) throw error;\n\n log.error(\"http\", \"auth\", error);\n return response.forbidden({\n error: t(\"auth.errors.csrfOriginMismatch\"),\n errorCode: AuthErrorCodes.CsrfOriginMismatch,\n });\n }\n }\n\n const outcome = await resolveRequestUserOutcome(request, response, {\n tokenFrom: usedTokenFrom,\n refreshCredential: usedRefresh,\n overlapMs: options?.refresh?.overlapMs,\n allowedTypes,\n renewalUserType,\n });\n\n if (outcome.user) {\n request.locals.user = outcome.user;\n return;\n }\n\n if (outcome.failure === \"forbidden\") {\n return response.forbidden({\n error: t(\"auth.errors.unauthorized\"),\n errorCode: AuthErrorCodes.Unauthorized,\n });\n }\n\n if (optional) return;\n\n if (outcome.failure === \"missing\") {\n return reject({\n error: t(\"auth.errors.missingAccessToken\"),\n errorCode: AuthErrorCodes.MissingAccessToken,\n });\n }\n\n if (outcome.failure === \"unauthorized\") {\n return reject({\n error: t(\"auth.errors.unauthorized\"),\n errorCode: AuthErrorCodes.Unauthorized,\n });\n }\n\n return reject({\n error: t(\"auth.errors.invalidAccessToken\"),\n errorCode: AuthErrorCodes.InvalidAccessToken,\n });\n };\n\n const descriptorSources: AuthMiddlewareDescriptorSource[] = dualSources\n ? [...(dualHeader ? ([\"header\"] as const) : []), ...dualCookies.map(descriptorSource)]\n : [descriptorSource(tokenFrom)];\n const descriptor: AuthMiddlewareDescriptor = Object.freeze({\n sources: Object.freeze(descriptorSources.map(source =>\n typeof source === \"string\" ? source : Object.freeze({ ...source }),\n )) as AuthMiddlewareDescriptorSource[],\n userTypes: Object.freeze([...allowedTypes]) as string[],\n });\n\n // Non-enumerable so the tag never shows up in logs, spreads or JSON.\n Object.defineProperty(auth, AUTH_MIDDLEWARE_DESCRIPTOR, { value: descriptor, enumerable: false });\n\n return auth;\n}\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { TokenFrom } from "../contracts/types.mjs";
|
|
|
2
2
|
import { AuthErrorCodes } from "../utils/auth-error-codes.mjs";
|
|
3
3
|
import { Request } from "@warlock.js/core";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/auth/src/middleware/csrf-origin-check.d.ts
|
|
6
6
|
/** Why {@link assertCsrfOriginAllowed} refused a request. */
|
|
7
7
|
type CsrfOriginMismatchReason = "origin-mismatch" | "referer-mismatch" | "missing-origin-and-referer";
|
|
8
8
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf-origin-check.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"csrf-origin-check.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/csrf-origin-check.ts"],"mappings":";;;;;;KAQY,wBAAA;AAAZ;;;;AAAoC;AAWpC;AAXA,cAWa,uBAAA,SAAgC,KAAA;EAAA,SAGf,MAAA,EAAQ,wBAAA;EAAA,SAF3B,IAAA,GAAI,cAAA,CAAA,kBAAA;cAEe,MAAA,EAAQ,wBAAA;AAAA;;;;;;;;iBAatB,uBAAA,CAAwB,SAAA,EAAW,SAAS,EAAE,MAAA;;;;;;AAbA;AAa9D;;;;;;;;AAA4E;AAuB5E;;;;iBAAgB,uBAAA,CAAwB,OAAgB,EAAP,OAAO"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AuthErrorCodes } from "../utils/auth-error-codes.mjs";
|
|
2
2
|
import { resolveCsrfOriginVerdict } from "@warlock.js/core";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/middleware/csrf-origin-check.ts
|
|
5
5
|
/** HTTP methods the CSRF Origin check exempts — reads, never state changes. */
|
|
6
6
|
const SAFE_METHODS = new Set([
|
|
7
7
|
"GET",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf-origin-check.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"csrf-origin-check.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/csrf-origin-check.ts"],"sourcesContent":["import { resolveCsrfOriginVerdict, type Request } from \"@warlock.js/core\";\nimport type { TokenFrom } from \"../contracts/types\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\n\n/** HTTP methods the CSRF Origin check exempts — reads, never state changes. */\nconst SAFE_METHODS = new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\n\n/** Why {@link assertCsrfOriginAllowed} refused a request. */\nexport type CsrfOriginMismatchReason =\n | \"origin-mismatch\"\n | \"referer-mismatch\"\n | \"missing-origin-and-referer\";\n\n/**\n * A cookie-authenticated, unsafe-method request whose `Origin`/`Referer`\n * failed the check. `reason` names exactly why,\n * so an error transformer or an incident log never has to string-match the\n * message.\n */\nexport class CsrfOriginMismatchError extends Error {\n readonly code = AuthErrorCodes.CsrfOriginMismatch;\n\n constructor(public readonly reason: CsrfOriginMismatchReason) {\n super(`CSRF Origin check failed: ${reason}`);\n this.name = \"CsrfOriginMismatchError\";\n }\n}\n\n/**\n * Whether a request is in scope for the CSRF Origin check at all — only a\n * `cookie:`-sourced credential on an unsafe method (POST/PUT/PATCH/DELETE) is.\n * Header-token auth and safe methods (GET/HEAD/OPTIONS) are always exempt:\n * only a cookie-sourced credential can be silently replayed cross-site by a\n * browser, so only it needs the check.\n */\nexport function requiresCsrfOriginCheck(tokenFrom: TokenFrom, method: string): boolean {\n return tokenFrom.startsWith(\"cookie:\") && !SAFE_METHODS.has(method.toUpperCase());\n}\n\n/**\n * CSRF Origin check for a cookie-authenticated unsafe-method request (lead\n * decision 3). Allowed when `Origin` — or, when `Origin` is absent, `Referer`\n * — names the request's own origin or an entry in `auth.csrf.allowedOrigins`.\n * Throws {@link CsrfOriginMismatchError} otherwise, including when BOTH\n * headers are absent.\n *\n * Delegates the actual same-origin/allowedOrigins/trustProxy-aware\n * comparison to `@warlock.js/core`'s `resolveCsrfOriginVerdict`\n * (`core/src/http/csrf-origin-policy.ts`) — SECURITY card 8a752ab2 moved that\n * logic down into `core` so this middleware's check and core's own default\n * CSRF-Origin guard (`core/src/http/csrf-default-guard.ts`, which now runs\n * even earlier, ahead of this middleware, for cookie-carrying unsafe-method\n * requests generally) share one implementation instead of two copies that\n * could drift apart.\n *\n * Callers gate this behind {@link requiresCsrfOriginCheck} — this function\n * itself does not re-check the token source or the method.\n */\nexport function assertCsrfOriginAllowed(request: Request): void {\n const verdict = resolveCsrfOriginVerdict(request);\n\n if (!verdict.allowed) {\n throw new CsrfOriginMismatchError(verdict.reason);\n }\n}\n"],"mappings":";;;;;AAKA,MAAM,eAAe,IAAI,IAAI;CAAC;CAAO;CAAQ;AAAS,CAAC;;;;;;;AAcvD,IAAa,0BAAb,cAA6C,MAAM;CAGjD,YAAY,AAAgB,QAAkC;EAC5D,MAAM,6BAA6B,QAAQ;EADjB;;EAE1B,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,SAAgB,wBAAwB,WAAsB,QAAyB;CACrF,OAAO,UAAU,WAAW,SAAS,KAAK,CAAC,aAAa,IAAI,OAAO,YAAY,CAAC;AAClF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,wBAAwB,SAAwB;CAC9D,MAAM,UAAU,yBAAyB,OAAO;CAEhD,IAAI,CAAC,QAAQ,SACX,MAAM,IAAI,wBAAwB,QAAQ,MAAM;AAEpD"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AUTH_MIDDLEWARE_DESCRIPTOR, AuthMiddlewareDescriptor, AuthMiddlewareDescriptorSource, authMiddleware } from "./auth.middleware.mjs";
|
|
2
2
|
import { CsrfOriginMismatchError, CsrfOriginMismatchReason, assertCsrfOriginAllowed, requiresCsrfOriginCheck } from "./csrf-origin-check.mjs";
|
|
3
|
-
import { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware } from "./login-throttle.middleware.mjs";
|
|
3
|
+
import { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware, throttleIdentifierHash } from "./login-throttle.middleware.mjs";
|
|
4
4
|
import { tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware } from "./one-time-token-throttle.middleware.mjs";
|
|
5
5
|
import { otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware } from "./otp-throttle.middleware.mjs";
|
|
6
6
|
import { requireVerifiedEmail } from "./require-verified-email.middleware.mjs";
|
package/esm/middleware/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CsrfOriginMismatchError, assertCsrfOriginAllowed, requiresCsrfOriginCheck } from "./csrf-origin-check.mjs";
|
|
2
2
|
import { AUTH_MIDDLEWARE_DESCRIPTOR, authMiddleware } from "./auth.middleware.mjs";
|
|
3
|
-
import { loginThrottleMiddleware } from "./login-throttle.middleware.mjs";
|
|
3
|
+
import { loginThrottleMiddleware, throttleIdentifierHash } from "./login-throttle.middleware.mjs";
|
|
4
4
|
import { tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware } from "./one-time-token-throttle.middleware.mjs";
|
|
5
5
|
import { otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware } from "./otp-throttle.middleware.mjs";
|
|
6
6
|
import { requireVerifiedEmail } from "./require-verified-email.middleware.mjs";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { config } from "@warlock.js/core";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/auth/src/middleware/localized-login-path.ts
|
|
4
4
|
/**
|
|
5
5
|
* Prefixes `loginPath` with `locale` when `web`'s locale-URL routing is
|
|
6
6
|
* active for that locale — so a guarded `/ar/admin` page redirects an
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localized-login-path.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"localized-login-path.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/localized-login-path.ts"],"sourcesContent":["import { config } from \"@warlock.js/core\";\n\n/**\n * Prefixes `loginPath` with `locale` when `web`'s locale-URL routing is\n * active for that locale — so a guarded `/ar/admin` page redirects an\n * anonymous request to `/ar/login`, not the bare `/login` (finding: the\n * `returnUrl` carried the locale, the login destination itself did not).\n *\n * `auth` is not on `web`'s dependency graph, and `web` is not on `auth`'s\n * (neither package declares the other, in either `dependencies` or\n * `peerDependencies`) — so this cannot import `web`'s\n * `resolveLocaleRouting()` / `withLocalePrefix()` / `isPrefixedLocale()`\n * (`web/src/routing/locale-routing.ts`, `web/src/routing/locale-prefixed-paths.ts`).\n * Instead it reads the SAME config keys those read — `web.localeRouting.strategy`,\n * `app.localeCodes`, `app.localeCode` — by convention rather than by import,\n * the same pattern `web/src/server/auth-cookie-name.ts` uses in the other\n * direction for `auth.cookie.name`.\n *\n * Left alone (returned unchanged) when:\n * - `loginPath` is absolute (`http://…`, `https://…`) — a redirect target\n * outside this app has no \"locale prefix\" to add.\n * - `loginPath` is already prefixed with a known locale code (an app that\n * configured `auth.pageAuth.loginPath` as `/ar/login` itself).\n * - `web.localeRouting.strategy` is `\"none\"` (the default) or unset.\n * - `locale` is not one of `app.localeCodes`, or — under\n * `\"prefix-except-default\"` — `locale` is the default locale.\n */\nexport function localizedLoginPath(loginPath: string, locale: string): string {\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(loginPath)) return loginPath;\n\n const strategy = config.key<string>(\"web.localeRouting.strategy\", \"none\");\n\n if (strategy !== \"prefix\" && strategy !== \"prefix-except-default\") return loginPath;\n\n const codes = config.key<string[]>(\"app.localeCodes\", []) ?? [];\n\n if (isAlreadyPrefixed(loginPath, codes)) return loginPath;\n\n if (!codes.includes(locale)) return loginPath;\n\n if (strategy === \"prefix-except-default\") {\n const defaultLocale = config.key<string>(\"app.localeCode\", \"\");\n\n if (locale === defaultLocale) return loginPath;\n }\n\n return loginPath === \"/\" ? `/${locale}` : `/${locale}${loginPath}`;\n}\n\n/** `true` when `path` already starts with one of `codes` as its own URL segment. */\nfunction isAlreadyPrefixed(path: string, codes: readonly string[]): boolean {\n return codes.some((code) => path === `/${code}` || path.startsWith(`/${code}/`));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,mBAAmB,WAAmB,QAAwB;CAC5E,IAAI,2BAA2B,KAAK,SAAS,GAAG,OAAO;CAEvD,MAAM,WAAW,OAAO,IAAY,8BAA8B,MAAM;CAExE,IAAI,aAAa,YAAY,aAAa,yBAAyB,OAAO;CAE1E,MAAM,QAAQ,OAAO,IAAc,mBAAmB,CAAC,CAAC,KAAK,CAAC;CAE9D,IAAI,kBAAkB,WAAW,KAAK,GAAG,OAAO;CAEhD,IAAI,CAAC,MAAM,SAAS,MAAM,GAAG,OAAO;CAEpC,IAAI,aAAa,yBAGf;MAAI,WAFkB,OAAO,IAAY,kBAAkB,EAEhC,GAAG,OAAO;CAAS;CAGhD,OAAO,cAAc,MAAM,IAAI,WAAW,IAAI,SAAS;AACzD;;AAGA,SAAS,kBAAkB,MAAc,OAAmC;CAC1E,OAAO,MAAM,MAAM,SAAS,SAAS,IAAI,UAAU,KAAK,WAAW,IAAI,KAAK,EAAE,CAAC;AACjF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Middleware, Request, Response } from "@warlock.js/core";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/auth/src/middleware/login-throttle.middleware.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Built-in identifier kinds the throttle knows how to extract from a request.
|
|
6
6
|
*
|
|
@@ -74,6 +74,21 @@ type LoginThrottleOptions = {
|
|
|
74
74
|
*/
|
|
75
75
|
identify?: (request: Request) => string[];
|
|
76
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Hash of a throttle identifier (`kind` is `"email"`, `"ip"`, ...). The value
|
|
79
|
+
* is trimmed and lower-cased first, so `" Sara@x.io"` and `"sara@x.io"` share
|
|
80
|
+
* one bucket.
|
|
81
|
+
*
|
|
82
|
+
* Identifiers become part of cache keys, and cache drivers log their keys: a
|
|
83
|
+
* raw email or IP there is personal data in a log line. A plain SHA-256 of an
|
|
84
|
+
* email is reversible by anyone with a list of addresses, so the hash is
|
|
85
|
+
* keyed (HMAC) with a key derived from `auth.accessToken.secret`. Without a
|
|
86
|
+
* configured secret it falls back to SHA-256, which still keeps the address
|
|
87
|
+
* out of the key.
|
|
88
|
+
*
|
|
89
|
+
* Exported for tests and for apps that inspect throttle keys.
|
|
90
|
+
*/
|
|
91
|
+
declare function throttleIdentifierHash(kind: string, value: string): string;
|
|
77
92
|
/**
|
|
78
93
|
* Failure-aware login throttle. Locks an identifier (account, IP, or both)
|
|
79
94
|
* after too many failed login attempts within a window — without penalising
|
|
@@ -112,5 +127,5 @@ type LoginThrottleOptions = {
|
|
|
112
127
|
*/
|
|
113
128
|
declare function loginThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
114
129
|
//#endregion
|
|
115
|
-
export { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware };
|
|
130
|
+
export { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware, throttleIdentifierHash };
|
|
116
131
|
//# sourceMappingURL=login-throttle.middleware.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-throttle.middleware.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"login-throttle.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/login-throttle.middleware.ts"],"mappings":";;;;;AAkBA;;;;AAAmC;AAYnC;;;KAZY,uBAAA;;;;;;;;;;;KAYA,oBAAA;EAkDV;;;;;EA5CA,GAAA;EAmD4B;AAAA;AAwC9B;;;EApFE,MAAA;EAoFgE;AA0IlE;;;;EAvNE,eAAA;EAuNsC;;;AAA+C;;EAhNrF,EAAA,GAAK,uBAAA;;;;;;;;EASL,aAAA;;;;EAKA,YAAA;;;;;;;;EASA,SAAA,IAAa,QAAA,EAAU,QAAA;;;;;;EAOvB,QAAA,IAAY,OAAA,EAAS,OAAA;AAAA;;;;;;;;;;;;;;;iBAwCP,sBAAA,CAAuB,IAAA,UAAc,KAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0IlD,uBAAA,CAAwB,OAAA,GAAS,oBAAA,GAA4B,UAAU"}
|
|
@@ -1,14 +1,88 @@
|
|
|
1
1
|
import { AuthErrorCodes } from "../utils/auth-error-codes.mjs";
|
|
2
|
+
import { authConfig } from "../services/auth-config.mjs";
|
|
2
3
|
import { t } from "@warlock.js/core";
|
|
4
|
+
import { createHash, createHmac } from "node:crypto";
|
|
3
5
|
import { log } from "@warlock.js/logger";
|
|
6
|
+
import ms from "ms";
|
|
4
7
|
import { cache } from "@warlock.js/cache";
|
|
5
8
|
|
|
6
|
-
//#region
|
|
9
|
+
//#region ../../packages/auth/src/middleware/login-throttle.middleware.ts
|
|
7
10
|
const COUNTER_PREFIX = "auth.throttle.count.";
|
|
8
11
|
const LOCK_PREFIX = "auth.throttle.lock.";
|
|
9
12
|
const counterKey = (identifier) => `${COUNTER_PREFIX}${identifier}`;
|
|
10
13
|
const lockKey = (identifier) => `${LOCK_PREFIX}${identifier}`;
|
|
11
14
|
/**
|
|
15
|
+
* The key a throttle identifier is hashed with: an HMAC key derived from
|
|
16
|
+
* `auth.accessToken.secret` (the same root the OTP code MAC uses), or
|
|
17
|
+
* `undefined` when no secret is configured.
|
|
18
|
+
*/
|
|
19
|
+
function throttleHashKey() {
|
|
20
|
+
let secret;
|
|
21
|
+
try {
|
|
22
|
+
secret = authConfig.accessToken.secret();
|
|
23
|
+
} catch {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
return createHmac("sha256", secret).update("@warlock.js/auth:throttle").digest();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Hash of a throttle identifier (`kind` is `"email"`, `"ip"`, ...). The value
|
|
30
|
+
* is trimmed and lower-cased first, so `" Sara@x.io"` and `"sara@x.io"` share
|
|
31
|
+
* one bucket.
|
|
32
|
+
*
|
|
33
|
+
* Identifiers become part of cache keys, and cache drivers log their keys: a
|
|
34
|
+
* raw email or IP there is personal data in a log line. A plain SHA-256 of an
|
|
35
|
+
* email is reversible by anyone with a list of addresses, so the hash is
|
|
36
|
+
* keyed (HMAC) with a key derived from `auth.accessToken.secret`. Without a
|
|
37
|
+
* configured secret it falls back to SHA-256, which still keeps the address
|
|
38
|
+
* out of the key.
|
|
39
|
+
*
|
|
40
|
+
* Exported for tests and for apps that inspect throttle keys.
|
|
41
|
+
*/
|
|
42
|
+
function throttleIdentifierHash(kind, value) {
|
|
43
|
+
const normalized = `${kind}:${value.trim().toLowerCase()}`;
|
|
44
|
+
const key = throttleHashKey();
|
|
45
|
+
return key ? createHmac("sha256", key).update(normalized).digest("hex") : createHash("sha256").update(normalized).digest("hex");
|
|
46
|
+
}
|
|
47
|
+
let warnedNullDriver = false;
|
|
48
|
+
/**
|
|
49
|
+
* The `null` cache driver stores nothing: every count reads back as 1 and no
|
|
50
|
+
* lock is ever found, so the throttle silently never trips. Say so once.
|
|
51
|
+
*/
|
|
52
|
+
function warnIfNullDriver() {
|
|
53
|
+
if (warnedNullDriver) return;
|
|
54
|
+
if (cache.currentDriver?.name !== "null") return;
|
|
55
|
+
warnedNullDriver = true;
|
|
56
|
+
log.warn("auth", "login-throttle", "The cache driver is `null`, so login, token and OTP throttles never lock. Use a real cache driver (redis, pg, or memory for one process).");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A lock value is the lockout deadline (epoch ms). `true`, written by 5.29
|
|
60
|
+
* and earlier, still counts as locked until the key expires.
|
|
61
|
+
*/
|
|
62
|
+
function isLocked(value) {
|
|
63
|
+
if (typeof value === "number") return value > Date.now();
|
|
64
|
+
return Boolean(value);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* `ms`'s honest runtime signature (see `services/auth-config.ts`): it returns
|
|
68
|
+
* `undefined` for text it cannot parse and throws for `""`.
|
|
69
|
+
*/
|
|
70
|
+
const parseMs = ms;
|
|
71
|
+
/**
|
|
72
|
+
* `"15m"` / `"1h"` / seconds as a number → seconds, or `undefined` when the
|
|
73
|
+
* value cannot be parsed (the cache rejects such a TTL anyway).
|
|
74
|
+
*/
|
|
75
|
+
function durationSeconds(duration) {
|
|
76
|
+
if (typeof duration === "number") return Number.isFinite(duration) && duration > 0 ? duration : void 0;
|
|
77
|
+
let milliseconds;
|
|
78
|
+
try {
|
|
79
|
+
milliseconds = parseMs(duration);
|
|
80
|
+
} catch {
|
|
81
|
+
milliseconds = void 0;
|
|
82
|
+
}
|
|
83
|
+
return typeof milliseconds === "number" && Number.isFinite(milliseconds) && milliseconds > 0 ? milliseconds / 1e3 : void 0;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
12
86
|
* Built-in identifier extractor — reads the configured credential field from
|
|
13
87
|
* the request body for the per-account key, and `request.detectIp()` for the
|
|
14
88
|
* per-source key. Untruthy values are dropped; a request that yields zero
|
|
@@ -18,11 +92,11 @@ function buildDefaultIdentifiers(request, tracked, credentialField) {
|
|
|
18
92
|
const identifiers = [];
|
|
19
93
|
if (tracked.includes("email")) {
|
|
20
94
|
const value = request.input(credentialField);
|
|
21
|
-
if (typeof value === "string" && value.length > 0) identifiers.push(`email.${value
|
|
95
|
+
if (typeof value === "string" && value.trim().length > 0) identifiers.push(`email.${throttleIdentifierHash("email", value)}`);
|
|
22
96
|
}
|
|
23
97
|
if (tracked.includes("ip")) {
|
|
24
98
|
const ip = request.detectIp();
|
|
25
|
-
if (ip) identifiers.push(`ip.${ip}`);
|
|
99
|
+
if (ip) identifiers.push(`ip.${throttleIdentifierHash("ip", ip)}`);
|
|
26
100
|
}
|
|
27
101
|
return identifiers;
|
|
28
102
|
}
|
|
@@ -71,8 +145,9 @@ function loginThrottleMiddleware(options = {}) {
|
|
|
71
145
|
return async ({ request, response }) => {
|
|
72
146
|
const identifiers = options.identify ? options.identify(request) : buildDefaultIdentifiers(request, tracked, credentialField);
|
|
73
147
|
if (identifiers.length === 0) return;
|
|
148
|
+
warnIfNullDriver();
|
|
74
149
|
try {
|
|
75
|
-
for (const identifier of identifiers) if (await cache.get(lockKey(identifier))) return response.tooManyRequests({
|
|
150
|
+
for (const identifier of identifiers) if (isLocked(await cache.get(lockKey(identifier)))) return response.tooManyRequests({
|
|
76
151
|
error: options.errorMessage ?? t("auth.errors.tooManyAttempts"),
|
|
77
152
|
errorCode: "EC004"
|
|
78
153
|
});
|
|
@@ -95,7 +170,10 @@ function loginThrottleMiddleware(options = {}) {
|
|
|
95
170
|
ttl: window,
|
|
96
171
|
onConflict: "create"
|
|
97
172
|
});
|
|
98
|
-
if (await cache.increment(key) >= max)
|
|
173
|
+
if (await cache.increment(key) >= max) {
|
|
174
|
+
const lockSeconds = durationSeconds(lockoutDuration);
|
|
175
|
+
await cache.set(lockKey(identifier), lockSeconds === void 0 ? true : Date.now() + lockSeconds * 1e3, { ttl: lockoutDuration });
|
|
176
|
+
}
|
|
99
177
|
}
|
|
100
178
|
} catch (error) {
|
|
101
179
|
log.error("auth", "login-throttle", error);
|
|
@@ -105,5 +183,5 @@ function loginThrottleMiddleware(options = {}) {
|
|
|
105
183
|
}
|
|
106
184
|
|
|
107
185
|
//#endregion
|
|
108
|
-
export { loginThrottleMiddleware };
|
|
186
|
+
export { loginThrottleMiddleware, throttleIdentifierHash };
|
|
109
187
|
//# sourceMappingURL=login-throttle.middleware.mjs.map
|