@warlock.js/auth 5.26.0 → 5.27.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 CHANGED
@@ -4,6 +4,12 @@ All notable changes to `@warlock.js/auth` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.27.0 - 2026-10-01
8
+
9
+ ### Added
10
+
11
+ - `authMiddleware()` now tags the middleware it returns with a non-enumerable, frozen, JSON-safe descriptor under `Symbol.for("warlock.auth")` (`{ sources, userTypes }`), exported as `AUTH_MIDDLEWARE_DESCRIPTOR` / `AuthMiddlewareDescriptor`, so other tooling can read a route's resolved credential sources and allowed user types without changing runtime behavior.
12
+
7
13
  ## 5.26.0 - 2026-09-30
8
14
 
9
15
  ### Added
package/esm/index.d.mts CHANGED
@@ -20,7 +20,7 @@ import { ProviderEmailNotVerifiedError } from "./errors/provider-email-not-verif
20
20
  import { AuthErrorTranslations, AuthTranslations } from "./localization/auth-translations.type.mjs";
21
21
  import { authDefaultTranslations } from "./localization/auth-default-translations.mjs";
22
22
  import { registerAuthTranslations } from "./localization/register-auth-translations.mjs";
23
- import { authMiddleware } from "./middleware/auth.middleware.mjs";
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
25
  import { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware } from "./middleware/login-throttle.middleware.mjs";
26
26
  import { tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware } from "./middleware/one-time-token-throttle.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, AccessToken, AccessTokenConfig, AccessTokenOutput, AppleJoseModule, AppleProvider, AppleProviderConfig, Auth, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthErrorCodes, AuthErrorTranslations, AuthEventCallback, AuthEventName, AuthEventPayloads, 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, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
58
58
  import "./contracts/request-locals.mjs";
package/esm/index.mjs CHANGED
@@ -26,7 +26,7 @@ import { ProviderEmailNotVerifiedError } from "./errors/provider-email-not-verif
26
26
  import "./errors/index.mjs";
27
27
  import "./localization/index.mjs";
28
28
  import { readCredential, resolveRequestUser, resolveRequestUserOutcome } from "./services/resolve-request-user.mjs";
29
- import { authMiddleware } from "./middleware/auth.middleware.mjs";
29
+ import { AUTH_MIDDLEWARE_DESCRIPTOR, authMiddleware } from "./middleware/auth.middleware.mjs";
30
30
  import { loginThrottleMiddleware } 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";
@@ -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, 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, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
@@ -2,6 +2,24 @@ import { AuthMiddlewareOptions, TokenFrom } from "../contracts/types.mjs";
2
2
  import { Middleware } from "@warlock.js/core";
3
3
 
4
4
  //#region ../auth/src/middleware/auth.middleware.d.ts
5
+ /**
6
+ * Well-known symbol under which `authMiddleware()` tags the middleware it
7
+ * returns. Shared via the global symbol registry so other packages can read the
8
+ * descriptor structurally (`Symbol.for("warlock.auth")`) without importing auth.
9
+ */
10
+ declare const AUTH_MIDDLEWARE_DESCRIPTOR: unique symbol;
11
+ /** A credential source an auth middleware reads: the Authorization header or a named cookie. */
12
+ type AuthMiddlewareDescriptorSource = "header" | {
13
+ cookie: string;
14
+ };
15
+ /**
16
+ * Frozen, JSON-safe description of how an `authMiddleware()` instance
17
+ * authenticates, stored (non-enumerably) under {@link AUTH_MIDDLEWARE_DESCRIPTOR}.
18
+ */
19
+ type AuthMiddlewareDescriptor = {
20
+ /** Resolved credential sources; with dual sources the header comes first. */sources: AuthMiddlewareDescriptorSource[]; /** Resolved allowed user types; an empty array means any authenticated user. */
21
+ userTypes: string[];
22
+ };
5
23
  /**
6
24
  * Build a route gate. Authentication is required unless optional is explicit.
7
25
  *
@@ -28,5 +46,5 @@ declare function authMiddleware(options: AuthMiddlewareOptions): Middleware;
28
46
  declare function authMiddleware(allowedUserType: string | string[], options?: AuthMiddlewareOptions): Middleware;
29
47
  declare function authMiddleware(allowedUserType: string | string[], tokenFrom?: TokenFrom): Middleware;
30
48
  //#endregion
31
- export { authMiddleware };
49
+ export { AUTH_MIDDLEWARE_DESCRIPTOR, AuthMiddlewareDescriptor, AuthMiddlewareDescriptorSource, authMiddleware };
32
50
  //# sourceMappingURL=auth.middleware.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"mappings":";;;;;;AA8GA;;;;AAA4C;AAC5C;;;;;;;;AAA0E;AAC1E;;;;;iBAFgB,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"}
1
+ {"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["../../../../../../../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"}
@@ -8,6 +8,12 @@ import { log } from "@warlock.js/logger";
8
8
 
9
9
  //#region ../auth/src/middleware/auth.middleware.ts
10
10
  /**
11
+ * Well-known symbol under which `authMiddleware()` tags the middleware it
12
+ * returns. Shared via the global symbol registry so other packages can read the
13
+ * descriptor structurally (`Symbol.for("warlock.auth")`) without importing auth.
14
+ */
15
+ const AUTH_MIDDLEWARE_DESCRIPTOR = Symbol.for("warlock.auth");
16
+ /**
11
17
  * Reject an unauthenticated request, choosing the representation by ROUTE KIND.
12
18
  *
13
19
  * An API route always gets the JSON 401 it has always gotten. A PAGE route
@@ -41,6 +47,9 @@ function tokenFromDescriptor(descriptor) {
41
47
  if (!descriptor.key) throw new Error("authMiddleware cookie credentials require a cookie key.");
42
48
  return `cookie:${descriptor.key}`;
43
49
  }
50
+ function descriptorSource(tokenFrom) {
51
+ return tokenFrom === "header" ? "header" : { cookie: tokenFrom.slice(7) };
52
+ }
44
53
  function resolveAllowedTypes(value) {
45
54
  if (value !== void 0) return Array.isArray(value) ? value : [value];
46
55
  const configuredDefault = config.key("auth.defaultUserType");
@@ -111,9 +120,18 @@ function authMiddleware(userTypeOrOptions, legacyTokenFromOrOptions) {
111
120
  errorCode: "EC002"
112
121
  });
113
122
  };
123
+ const descriptorSources = dualSources ? [...dualHeader ? ["header"] : [], ...dualCookies.map(descriptorSource)] : [descriptorSource(tokenFrom)];
124
+ const descriptor = Object.freeze({
125
+ sources: Object.freeze(descriptorSources.map((source) => typeof source === "string" ? source : Object.freeze({ ...source }))),
126
+ userTypes: Object.freeze([...allowedTypes])
127
+ });
128
+ Object.defineProperty(auth, AUTH_MIDDLEWARE_DESCRIPTOR, {
129
+ value: descriptor,
130
+ enumerable: false
131
+ });
114
132
  return auth;
115
133
  }
116
134
 
117
135
  //#endregion
118
- export { authMiddleware };
136
+ export { AUTH_MIDDLEWARE_DESCRIPTOR, authMiddleware };
119
137
  //# sourceMappingURL=auth.middleware.mjs.map
@@ -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/** 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 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 return auth;\n}\n"],"mappings":"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"}
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":";;;;;;;;;;;;;;AAsBA,MAAa,6BAA6B,OAAO,IAAI,cAAc;;;;;;;;;;;;;;;;;;;AAqCnE,SAAS,mBACP,EAAE,SAAS,YACX,SACA,UACA;CACA,MAAM,YAAY,UAAU,MAAM,WAAW,SAAS,UAAU;CAChE,MAAM,iBAAiB,UAAU,kBAAkB,WAAW,SAAS,eAAe;CAEtF,IAAI,aAAa,QAAQ,OAAO,QAAQ;EAMtC,MAAM,cAAc,mBAAmB,WAAW,QAAQ,MAAM;EAChE,MAAM,YAAY,YAAY,SAAS,GAAG,IAAI,MAAM;EACpD,MAAM,YAAY,mBAAmB,QAAQ,GAAG;EAEhD,OAAO,SAAS,SAAS,GAAG,cAAc,YAAY,eAAe,GAAG,WAAW;CACrF;CAEA,OAAO,SAAS,aAAa,OAAO;AACtC;AAEA,SAAS,oBAAoB,YAA0D;CACrF,IAAI,CAAC,WAAW,UAAU,WAAW,WAAW,UAAU,OAAO;CAEjE,IAAI,CAAC,WAAW,KACd,MAAM,IAAI,MAAM,yDAAyD;CAG3E,OAAO,UAAU,WAAW;AAC9B;AAEA,SAAS,iBAAiB,WAAsD;CAC9E,OAAO,cAAc,WAAW,WAAW,EAAE,QAAQ,UAAU,MAAM,CAAgB,EAAE;AACzF;AAEA,SAAS,oBAAoB,OAAgD;CAC3E,IAAI,UAAU,QAAW,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CAErE,MAAM,oBAAoB,OAAO,IAAI,sBAAsB;CAE3D,IAAI,mBAAmB,OAAO,CAAC,iBAAiB;CAEhD,MAAM,YAAY,OAAO,IAAI,iBAAiB,CAAC,CAAC;CAChD,MAAM,kBAAkB,OAAO,KAAK,SAAS;CAE7C,IAAI,gBAAgB,WAAW,GAAG,OAAO;CAEzC,MAAM,IAAI,MACR,uGACF;AACF;AAiCA,SAAgB,eACd,mBACA,0BACY;CACZ,MAAM,UACJ,OAAO,sBAAsB,YAAY,CAAC,MAAM,QAAQ,iBAAiB,IACrE,oBACA,OAAO,6BAA6B,WAClC,2BACA;CAKR,MAAM,eAAe,oBAHnB,OAAO,sBAAsB,YAAY,MAAM,QAAQ,iBAAiB,IACpE,oBACA,MACmD;CACzD,MAAM,kBACJ,OAAO,6BAA6B,WAAW,2BAA2B;CAC5E,MAAM,cAAc,SAAS,SAAS,SAAS,QAAQ,QAAQ,IAAI,mBAAmB,IAAI;CAE1F,IAAI,eAAe,SAAS,QAC1B,MAAM,IAAI,MAAM,gEAAgE;CAGlF,MAAM,aAAa,aAAa,SAAS,QAAQ,KAAK;CACtD,MAAM,cAAc,aAAa,QAAO,WAAU,WAAW,QAAQ,KAAK,CAAC;CAE3E,IAAI,gBAAgB,YAAY,SAAS,KAAK,YAAY,SAAS,YAAY,SAAS,IACtF,MAAM,IAAI,MAAM,qEAAqE;CAUvF,MAAM,YAL6B,cAC9B,YAAY,MAAM,WACnB,UACE,oBAAoB,OAAO,IAC1B,mBAAmB;CAE1B,MAAM,oBAAoB,SAAS,UAAU,oBAAoB,QAAQ,OAAO,IAAI;CACpF,MAAM,kBAAkB,aAAa,WAAW,IAAI,aAAa,KAAK;CAEtE,IACE,sBACC,cAAc,YAAY,sBAAsB,YAAY,CAAC,kBAE9D,MAAM,IAAI,MACR,4GACF;CAGF,MAAM,WAAW,SAAS,aAAa;CAEvC,MAAM,OAAmB,OAAO,EAAE,SAAS,eAAe;EACxD,MAAM,UAAU,YACd,mBAAmB;GAAE;GAAS;EAAS,GAAG,SAAS,SAAS,QAAQ;EAItE,MAAM,aAAa,cAAc,QAAQ,QAAQ,kBAAkB;EACnE,MAAM,gBAA2B,aAAa,WAAW;EACzD,MAAM,cAAc,aAAa,SAAY;EAI7C,KACG,eAAe,SAAS,aAAa,KACnC,eAAe,eAAe,SAAS,WAAW,MACrD,wBAAwB,eAAe,QAAQ,MAAM,GAErD,IAAI;GACF,wBAAwB,OAAO;EACjC,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,0BAA0B,MAAM;GAEvD,IAAI,MAAM,QAAQ,QAAQ,KAAK;GAC/B,OAAO,SAAS,UAAU;IACxB,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;EAGF,MAAM,UAAU,MAAM,0BAA0B,SAAS,UAAU;GACjE,WAAW;GACX,mBAAmB;GACnB,WAAW,SAAS,SAAS;GAC7B;GACA;EACF,CAAC;EAED,IAAI,QAAQ,MAAM;GAChB,QAAQ,OAAO,OAAO,QAAQ;GAC9B;EACF;EAEA,IAAI,QAAQ,YAAY,aACtB,OAAO,SAAS,UAAU;GACxB,OAAO,EAAE,0BAA0B;GACnC;EACF,CAAC;EAGH,IAAI,UAAU;EAEd,IAAI,QAAQ,YAAY,WACtB,OAAO,OAAO;GACZ,OAAO,EAAE,gCAAgC;GACzC;EACF,CAAC;EAGH,IAAI,QAAQ,YAAY,gBACtB,OAAO,OAAO;GACZ,OAAO,EAAE,0BAA0B;GACnC;EACF,CAAC;EAGH,OAAO,OAAO;GACZ,OAAO,EAAE,gCAAgC;GACzC;EACF,CAAC;CACH;CAEA,MAAM,oBAAsD,cACxD,CAAC,GAAI,aAAc,CAAC,QAAQ,IAAc,CAAC,GAAI,GAAG,YAAY,IAAI,gBAAgB,CAAC,IACnF,CAAC,iBAAiB,SAAS,CAAC;CAChC,MAAM,aAAuC,OAAO,OAAO;EACzD,SAAS,OAAO,OAAO,kBAAkB,KAAI,WAC3C,OAAO,WAAW,WAAW,SAAS,OAAO,OAAO,EAAE,GAAG,OAAO,CAAC,CACnE,CAAC;EACD,WAAW,OAAO,OAAO,CAAC,GAAG,YAAY,CAAC;CAC5C,CAAC;CAGD,OAAO,eAAe,MAAM,4BAA4B;EAAE,OAAO;EAAY,YAAY;CAAM,CAAC;CAEhG,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { authMiddleware } from "./auth.middleware.mjs";
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
3
  import { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware } from "./login-throttle.middleware.mjs";
4
4
  import { tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware } from "./one-time-token-throttle.middleware.mjs";
@@ -1,5 +1,5 @@
1
1
  import { CsrfOriginMismatchError, assertCsrfOriginAllowed, requiresCsrfOriginCheck } from "./csrf-origin-check.mjs";
2
- import { authMiddleware } from "./auth.middleware.mjs";
2
+ import { AUTH_MIDDLEWARE_DESCRIPTOR, authMiddleware } from "./auth.middleware.mjs";
3
3
  import { loginThrottleMiddleware } 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";
package/llms-full.txt CHANGED
@@ -1519,6 +1519,10 @@ export default {
1519
1519
 
1520
1520
  This is an Origin check, not a double-submit CSRF token — cheaper, and closes the gap `SameSite=Lax` leaves open, but it doesn't help a client whose privacy proxy strips both `Origin` and `Referer` (that request is rejected outright, fail-closed, rather than silently allowed). A double-submit token mechanism is deferred to a later release; track it if your threat model includes Origin-stripping clients.
1521
1521
 
1522
+ ## Tooling can read how a route authenticates
1523
+
1524
+ `authMiddleware()` tags the function it returns with a frozen, non-enumerable descriptor under `Symbol.for("warlock.auth")` (exported as `AUTH_MIDDLEWARE_DESCRIPTOR`, typed `AuthMiddlewareDescriptor`): `{ sources: ("header" | { cookie: string })[], userTypes: string[] }`. `warlock generate.openapi` reads it to emit bearer/cookie security for guarded routes. It never appears in logs, spreads or JSON, and changes nothing at runtime. Custom guards without it are simply documented as unauthenticated.
1525
+
1522
1526
  ## Things NOT to do
1523
1527
 
1524
1528
  - Don't call `authMiddleware` outside route definition. It returns a function — the function is what runs per-request. Calling it once per request creates a fresh middleware on every hit (wasteful) and a fresh allowed-types Set (correctness if the input changes per call).
package/package.json CHANGED
@@ -12,13 +12,13 @@
12
12
  "ms": "^2.1.3"
13
13
  },
14
14
  "peerDependencies": {
15
- "@warlock.js/fs": "5.26.0",
16
- "@warlock.js/cache": "5.26.0",
17
- "@warlock.js/cascade": "5.26.0",
18
- "@warlock.js/core": "5.26.0",
19
- "@warlock.js/logger": "5.26.0",
20
- "@warlock.js/seal": "5.26.0",
21
- "@warlock.js/notifications": "5.26.0",
15
+ "@warlock.js/fs": "5.27.0",
16
+ "@warlock.js/cache": "5.27.0",
17
+ "@warlock.js/cascade": "5.27.0",
18
+ "@warlock.js/core": "5.27.0",
19
+ "@warlock.js/logger": "5.27.0",
20
+ "@warlock.js/seal": "5.27.0",
21
+ "@warlock.js/notifications": "5.27.0",
22
22
  "@simplewebauthn/server": "^13.1.0",
23
23
  "jose": "^6.1.0"
24
24
  },
@@ -47,7 +47,7 @@
47
47
  ],
48
48
  "author": "hassanzohdy",
49
49
  "license": "MIT",
50
- "version": "5.26.0",
50
+ "version": "5.27.0",
51
51
  "type": "module",
52
52
  "main": "./esm/index.mjs",
53
53
  "module": "./esm/index.mjs",
@@ -284,6 +284,10 @@ export default {
284
284
 
285
285
  This is an Origin check, not a double-submit CSRF token — cheaper, and closes the gap `SameSite=Lax` leaves open, but it doesn't help a client whose privacy proxy strips both `Origin` and `Referer` (that request is rejected outright, fail-closed, rather than silently allowed). A double-submit token mechanism is deferred to a later release; track it if your threat model includes Origin-stripping clients.
286
286
 
287
+ ## Tooling can read how a route authenticates
288
+
289
+ `authMiddleware()` tags the function it returns with a frozen, non-enumerable descriptor under `Symbol.for("warlock.auth")` (exported as `AUTH_MIDDLEWARE_DESCRIPTOR`, typed `AuthMiddlewareDescriptor`): `{ sources: ("header" | { cookie: string })[], userTypes: string[] }`. `warlock generate.openapi` reads it to emit bearer/cookie security for guarded routes. It never appears in logs, spreads or JSON, and changes nothing at runtime. Custom guards without it are simply documented as unauthenticated.
290
+
287
291
  ## Things NOT to do
288
292
 
289
293
  - Don't call `authMiddleware` outside route definition. It returns a function — the function is what runs per-request. Calling it once per request creates a fresh middleware on every hit (wasteful) and a fresh allowed-types Set (correctness if the input changes per call).