@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-throttle.middleware.mjs","names":[],"sources":["../../../../../../../auth/src/middleware/login-throttle.middleware.ts"],"sourcesContent":["import { cache } from \"@warlock.js/cache\";\nimport { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\n\n/**\n * Built-in identifier kinds the throttle knows how to extract from a request.\n *\n * - `\"email\"` tracks per-account, so an attacker can't spray one user from\n * many IPs.\n * - `\"ip\"` tracks per-source, so an attacker can't stuff many accounts from\n * one IP.\n *\n * Lockout trips when ANY tracked identifier crosses `max` — defense-in-depth.\n */\nexport type LoginThrottleIdentifier = \"email\" | \"ip\";\n\n/**\n * Options for {@link loginThrottleMiddleware}.\n *\n * Failure-aware by design: only counts requests whose response is non-2xx\n * (or whatever `isFailure` says) and resets on success, so a user who types\n * a wrong password three times then the right one is not penalised.\n *\n * @example\n * loginThrottleMiddleware({ max: 5, window: \"15m\", lockoutDuration: \"15m\" });\n */\nexport type LoginThrottleOptions = {\n /**\n * Failures allowed within the window before the identifier is locked.\n *\n * @default 5\n */\n max?: number;\n\n /**\n * Counting window. `ms`-format string (`\"15m\"`, `\"1h\"`) or seconds as a number.\n *\n * @default \"15m\"\n */\n window?: string | number;\n\n /**\n * Lockout duration once the threshold is tripped. `ms`-format string or seconds.\n *\n * @default \"15m\"\n */\n lockoutDuration?: string | number;\n\n /**\n * Identifiers tracked. Each is counted and locked independently.\n *\n * @default [\"email\", \"ip\"]\n */\n by?: LoginThrottleIdentifier[];\n\n /**\n * Credential field consulted when `\"email\"` is tracked. Switch to\n * `\"username\"` (or any other input field) for systems that don't\n * authenticate by email.\n *\n * @default \"email\"\n */\n identifierKey?: string;\n\n /**\n * Override the default error message returned with the 429 response.\n */\n errorMessage?: string;\n\n /**\n * Decide whether the response represents a failed login. Defaults to\n * \"non-2xx is a failure\"; override if your controller signals failure\n * through a 200-style body.\n *\n * @default (response) => !response.isOk\n */\n isFailure?: (response: Response) => boolean;\n\n /**\n * Compute the identifiers tracked for this request. Override to derive the\n * account key from a header, a JWT subject, or any other shape that does\n * not match the built-in `email` / `ip` extraction.\n */\n identify?: (request: Request) => string[];\n};\n\nconst COUNTER_PREFIX = \"auth.throttle.count.\";\nconst LOCK_PREFIX = \"auth.throttle.lock.\";\n\nconst counterKey = (identifier: string): string => `${COUNTER_PREFIX}${identifier}`;\nconst lockKey = (identifier: string): string => `${LOCK_PREFIX}${identifier}`;\n\n/**\n * Built-in identifier extractor — reads the configured credential field from\n * the request body for the per-account key, and `request.detectIp()` for the\n * per-source key. Untruthy values are dropped; a request that yields zero\n * identifiers is left untracked so the middleware no-ops for it.\n */\nfunction buildDefaultIdentifiers(\n request: Request,\n tracked: LoginThrottleIdentifier[],\n credentialField: string,\n): string[] {\n const identifiers: string[] = [];\n\n if (tracked.includes(\"email\")) {\n const value = request.input(credentialField);\n\n if (typeof value === \"string\" && value.length > 0) {\n identifiers.push(`email.${value.toLowerCase()}`);\n }\n }\n\n if (tracked.includes(\"ip\")) {\n const ip = request.detectIp();\n\n if (ip) {\n identifiers.push(`ip.${ip}`);\n }\n }\n\n return identifiers;\n}\n\n/**\n * Failure-aware login throttle. Locks an identifier (account, IP, or both)\n * after too many failed login attempts within a window — without penalising\n * a user who eventually types the right password.\n *\n * Mechanism (per identifier):\n *\n * 1. **Before the controller** — reject with `429` if a lock key is set,\n * skipping the DB lookup AND the bcrypt verify (this also neutralises the\n * CPU-DoS angle of brute-forcing).\n * 2. **After the controller** — observe the outcome via `response.onSent`.\n * On failure (non-2xx by default), bump a fixed-window counter; once it\n * reaches `max`, set a lock key with `lockoutDuration` TTL. On success,\n * clear both the counter and the lock for that identifier.\n *\n * Storage is `@warlock.js/cache` (a peer dependency of `@warlock.js/auth`,\n * transitively guaranteed via `@warlock.js/core`). Lockout state is shared\n * across replicas — correct security model, because an attacker can't bypass\n * by round-robin to a different pod. Every cache call is guarded so a\n * misconfigured driver fails open (a throttle outage must never escalate\n * into a full auth outage).\n *\n * @example\n * import { loginThrottleMiddleware } from \"@warlock.js/auth\";\n * import { router } from \"@warlock.js/core\";\n *\n * router.post(\"/auth/login\", loginController, {\n * middleware: [loginThrottleMiddleware({ max: 5, window: \"15m\" })],\n * });\n *\n * @example\n * // Per-IP only — avoid account-lockout-as-DoS on anonymous-attack vectors\n * router.post(\"/auth/refresh-token\", refreshController, {\n * middleware: [loginThrottleMiddleware({ by: [\"ip\"], max: 10, window: \"1m\" })],\n * });\n */\nexport function loginThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\n const max = options.max ?? 5;\n const window = options.window ?? \"15m\";\n const lockoutDuration = options.lockoutDuration ?? \"15m\";\n const tracked = options.by ?? [\"email\", \"ip\"];\n const credentialField = options.identifierKey ?? \"email\";\n\n return async ({ request, response }) => {\n const identifiers = options.identify\n ? options.identify(request)\n : buildDefaultIdentifiers(request, tracked, credentialField);\n\n if (identifiers.length === 0) return;\n\n try {\n for (const identifier of identifiers) {\n const locked = await cache.get(lockKey(identifier));\n\n if (locked) {\n return response.tooManyRequests({\n error: options.errorMessage ?? t(\"auth.errors.tooManyAttempts\"),\n errorCode: AuthErrorCodes.TooManyAttempts,\n });\n }\n }\n } catch (error) {\n // Fail open — a cache outage (driver not initialised, network blip)\n // must never escalate into a full auth outage. Log and let the request\n // through; the onSent recorder below is independently guarded so an\n // outage there is also harmless.\n log.error(\"auth\", \"login-throttle\", error);\n\n return;\n }\n\n response.onSent(async (resolvedResponse: Response) => {\n try {\n const failed = options.isFailure\n ? options.isFailure(resolvedResponse)\n : !resolvedResponse.isOk;\n\n if (!failed) {\n for (const identifier of identifiers) {\n await cache.remove(counterKey(identifier));\n await cache.remove(lockKey(identifier));\n }\n\n return;\n }\n\n for (const identifier of identifiers) {\n const key = counterKey(identifier);\n\n // Open the window on the first failure: the create-only write means\n // only one of several racing servers sets the TTL, and an existing\n // window keeps its deadline (fixed window). The bump itself is an\n // atomic increment, so concurrent failures across servers are never\n // lost (redis INCRBY / pg upsert keep the window's TTL).\n await cache.set(key, 0, { ttl: window, onConflict: \"create\" });\n const count = await cache.increment(key);\n\n if (count >= max) {\n await cache.set(lockKey(identifier), true, { ttl: lockoutDuration });\n }\n }\n } catch (error) {\n log.error(\"auth\", \"login-throttle\", error);\n }\n });\n };\n}\n"],"mappings":";;;;;;AAuFA,MAAM,iBAAiB;AACvB,MAAM,cAAc;AAEpB,MAAM,cAAc,eAA+B,GAAG,iBAAiB;AACvE,MAAM,WAAW,eAA+B,GAAG,cAAc;;;;;;;AAQjE,SAAS,wBACP,SACA,SACA,iBACU;CACV,MAAM,cAAwB,CAAC;CAE/B,IAAI,QAAQ,SAAS,OAAO,GAAG;EAC7B,MAAM,QAAQ,QAAQ,MAAM,eAAe;EAE3C,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAC9C,YAAY,KAAK,SAAS,MAAM,YAAY,GAAG;CAEnD;CAEA,IAAI,QAAQ,SAAS,IAAI,GAAG;EAC1B,MAAM,KAAK,QAAQ,SAAS;EAE5B,IAAI,IACF,YAAY,KAAK,MAAM,IAAI;CAE/B;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,wBAAwB,UAAgC,CAAC,GAAe;CACtF,MAAM,MAAM,QAAQ,OAAO;CAC3B,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,kBAAkB,QAAQ,mBAAmB;CACnD,MAAM,UAAU,QAAQ,MAAM,CAAC,SAAS,IAAI;CAC5C,MAAM,kBAAkB,QAAQ,iBAAiB;CAEjD,OAAO,OAAO,EAAE,SAAS,eAAe;EACtC,MAAM,cAAc,QAAQ,WACxB,QAAQ,SAAS,OAAO,IACxB,wBAAwB,SAAS,SAAS,eAAe;EAE7D,IAAI,YAAY,WAAW,GAAG;EAE9B,IAAI;GACF,KAAK,MAAM,cAAc,aAGvB,IAAI,MAFiB,MAAM,IAAI,QAAQ,UAAU,CAAC,GAGhD,OAAO,SAAS,gBAAgB;IAC9B,OAAO,QAAQ,gBAAgB,EAAE,6BAA6B;IAC9D;GACF,CAAC;EAGP,SAAS,OAAO;GAKd,IAAI,MAAM,QAAQ,kBAAkB,KAAK;GAEzC;EACF;EAEA,SAAS,OAAO,OAAO,qBAA+B;GACpD,IAAI;IAKF,IAAI,EAJW,QAAQ,YACnB,QAAQ,UAAU,gBAAgB,IAClC,CAAC,iBAAiB,OAET;KACX,KAAK,MAAM,cAAc,aAAa;MACpC,MAAM,MAAM,OAAO,WAAW,UAAU,CAAC;MACzC,MAAM,MAAM,OAAO,QAAQ,UAAU,CAAC;KACxC;KAEA;IACF;IAEA,KAAK,MAAM,cAAc,aAAa;KACpC,MAAM,MAAM,WAAW,UAAU;KAOjC,MAAM,MAAM,IAAI,KAAK,GAAG;MAAE,KAAK;MAAQ,YAAY;KAAS,CAAC;KAG7D,IAAI,MAFgB,MAAM,UAAU,GAAG,KAE1B,KACX,MAAM,MAAM,IAAI,QAAQ,UAAU,GAAG,MAAM,EAAE,KAAK,gBAAgB,CAAC;IAEvE;GACF,SAAS,OAAO;IACd,IAAI,MAAM,QAAQ,kBAAkB,KAAK;GAC3C;EACF,CAAC;CACH;AACF"}
|
|
1
|
+
{"version":3,"file":"login-throttle.middleware.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/login-throttle.middleware.ts"],"sourcesContent":["import { createHash, createHmac } from \"node:crypto\";\nimport { cache } from \"@warlock.js/cache\";\nimport { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport ms from \"ms\";\nimport { authConfig } from \"../services/auth-config\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\n\n/**\n * Built-in identifier kinds the throttle knows how to extract from a request.\n *\n * - `\"email\"` tracks per-account, so an attacker can't spray one user from\n * many IPs.\n * - `\"ip\"` tracks per-source, so an attacker can't stuff many accounts from\n * one IP.\n *\n * Lockout trips when ANY tracked identifier crosses `max` — defense-in-depth.\n */\nexport type LoginThrottleIdentifier = \"email\" | \"ip\";\n\n/**\n * Options for {@link loginThrottleMiddleware}.\n *\n * Failure-aware by design: only counts requests whose response is non-2xx\n * (or whatever `isFailure` says) and resets on success, so a user who types\n * a wrong password three times then the right one is not penalised.\n *\n * @example\n * loginThrottleMiddleware({ max: 5, window: \"15m\", lockoutDuration: \"15m\" });\n */\nexport type LoginThrottleOptions = {\n /**\n * Failures allowed within the window before the identifier is locked.\n *\n * @default 5\n */\n max?: number;\n\n /**\n * Counting window. `ms`-format string (`\"15m\"`, `\"1h\"`) or seconds as a number.\n *\n * @default \"15m\"\n */\n window?: string | number;\n\n /**\n * Lockout duration once the threshold is tripped. `ms`-format string or seconds.\n *\n * @default \"15m\"\n */\n lockoutDuration?: string | number;\n\n /**\n * Identifiers tracked. Each is counted and locked independently.\n *\n * @default [\"email\", \"ip\"]\n */\n by?: LoginThrottleIdentifier[];\n\n /**\n * Credential field consulted when `\"email\"` is tracked. Switch to\n * `\"username\"` (or any other input field) for systems that don't\n * authenticate by email.\n *\n * @default \"email\"\n */\n identifierKey?: string;\n\n /**\n * Override the default error message returned with the 429 response.\n */\n errorMessage?: string;\n\n /**\n * Decide whether the response represents a failed login. Defaults to\n * \"non-2xx is a failure\"; override if your controller signals failure\n * through a 200-style body.\n *\n * @default (response) => !response.isOk\n */\n isFailure?: (response: Response) => boolean;\n\n /**\n * Compute the identifiers tracked for this request. Override to derive the\n * account key from a header, a JWT subject, or any other shape that does\n * not match the built-in `email` / `ip` extraction.\n */\n identify?: (request: Request) => string[];\n};\n\nconst COUNTER_PREFIX = \"auth.throttle.count.\";\nconst LOCK_PREFIX = \"auth.throttle.lock.\";\n\nconst counterKey = (identifier: string): string => `${COUNTER_PREFIX}${identifier}`;\nconst lockKey = (identifier: string): string => `${LOCK_PREFIX}${identifier}`;\n\n/**\n * The key a throttle identifier is hashed with: an HMAC key derived from\n * `auth.accessToken.secret` (the same root the OTP code MAC uses), or\n * `undefined` when no secret is configured.\n */\nfunction throttleHashKey(): Buffer | undefined {\n let secret: string | undefined;\n\n try {\n secret = authConfig.accessToken.secret();\n } catch {\n return undefined;\n }\n\n return createHmac(\"sha256\", secret).update(\"@warlock.js/auth:throttle\").digest();\n}\n\n/**\n * Hash of a throttle identifier (`kind` is `\"email\"`, `\"ip\"`, ...). The value\n * is trimmed and lower-cased first, so `\" Sara@x.io\"` and `\"sara@x.io\"` share\n * one bucket.\n *\n * Identifiers become part of cache keys, and cache drivers log their keys: a\n * raw email or IP there is personal data in a log line. A plain SHA-256 of an\n * email is reversible by anyone with a list of addresses, so the hash is\n * keyed (HMAC) with a key derived from `auth.accessToken.secret`. Without a\n * configured secret it falls back to SHA-256, which still keeps the address\n * out of the key.\n *\n * Exported for tests and for apps that inspect throttle keys.\n */\nexport function throttleIdentifierHash(kind: string, value: string): string {\n const normalized = `${kind}:${value.trim().toLowerCase()}`;\n const key = throttleHashKey();\n\n return key\n ? createHmac(\"sha256\", key).update(normalized).digest(\"hex\")\n : createHash(\"sha256\").update(normalized).digest(\"hex\");\n}\n\nlet warnedNullDriver = false;\n\n/**\n * The `null` cache driver stores nothing: every count reads back as 1 and no\n * lock is ever found, so the throttle silently never trips. Say so once.\n */\nfunction warnIfNullDriver(): void {\n if (warnedNullDriver) return;\n\n const driverName = (cache as { currentDriver?: { name?: string } }).currentDriver?.name;\n\n if (driverName !== \"null\") return;\n\n warnedNullDriver = true;\n log.warn(\n \"auth\",\n \"login-throttle\",\n \"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).\",\n );\n}\n\n/**\n * A lock value is the lockout deadline (epoch ms). `true`, written by 5.29\n * and earlier, still counts as locked until the key expires.\n */\nfunction isLocked(value: unknown): boolean {\n if (typeof value === \"number\") {\n return value > Date.now();\n }\n\n return Boolean(value);\n}\n\n/**\n * `ms`'s honest runtime signature (see `services/auth-config.ts`): it returns\n * `undefined` for text it cannot parse and throws for `\"\"`.\n */\nconst parseMs = ms as unknown as (value: unknown) => number | string | undefined;\n\n/**\n * `\"15m\"` / `\"1h\"` / seconds as a number → seconds, or `undefined` when the\n * value cannot be parsed (the cache rejects such a TTL anyway).\n */\nfunction durationSeconds(duration: string | number): number | undefined {\n if (typeof duration === \"number\") {\n return Number.isFinite(duration) && duration > 0 ? duration : undefined;\n }\n\n let milliseconds: number | string | undefined;\n\n try {\n milliseconds = parseMs(duration);\n } catch {\n milliseconds = undefined;\n }\n\n return typeof milliseconds === \"number\" && Number.isFinite(milliseconds) && milliseconds > 0\n ? milliseconds / 1000\n : undefined;\n}\n\n/**\n * Built-in identifier extractor — reads the configured credential field from\n * the request body for the per-account key, and `request.detectIp()` for the\n * per-source key. Untruthy values are dropped; a request that yields zero\n * identifiers is left untracked so the middleware no-ops for it.\n */\nfunction buildDefaultIdentifiers(\n request: Request,\n tracked: LoginThrottleIdentifier[],\n credentialField: string,\n): string[] {\n const identifiers: string[] = [];\n\n if (tracked.includes(\"email\")) {\n const value = request.input(credentialField);\n\n if (typeof value === \"string\" && value.trim().length > 0) {\n identifiers.push(`email.${throttleIdentifierHash(\"email\", value)}`);\n }\n }\n\n if (tracked.includes(\"ip\")) {\n const ip = request.detectIp();\n\n if (ip) {\n identifiers.push(`ip.${throttleIdentifierHash(\"ip\", ip)}`);\n }\n }\n\n return identifiers;\n}\n\n/**\n * Failure-aware login throttle. Locks an identifier (account, IP, or both)\n * after too many failed login attempts within a window — without penalising\n * a user who eventually types the right password.\n *\n * Mechanism (per identifier):\n *\n * 1. **Before the controller** — reject with `429` if a lock key is set,\n * skipping the DB lookup AND the bcrypt verify (this also neutralises the\n * CPU-DoS angle of brute-forcing).\n * 2. **After the controller** — observe the outcome via `response.onSent`.\n * On failure (non-2xx by default), bump a fixed-window counter; once it\n * reaches `max`, set a lock key with `lockoutDuration` TTL. On success,\n * clear both the counter and the lock for that identifier.\n *\n * Storage is `@warlock.js/cache` (a peer dependency of `@warlock.js/auth`,\n * transitively guaranteed via `@warlock.js/core`). Lockout state is shared\n * across replicas — correct security model, because an attacker can't bypass\n * by round-robin to a different pod. Every cache call is guarded so a\n * misconfigured driver fails open (a throttle outage must never escalate\n * into a full auth outage).\n *\n * @example\n * import { loginThrottleMiddleware } from \"@warlock.js/auth\";\n * import { router } from \"@warlock.js/core\";\n *\n * router.post(\"/auth/login\", loginController, {\n * middleware: [loginThrottleMiddleware({ max: 5, window: \"15m\" })],\n * });\n *\n * @example\n * // Per-IP only — avoid account-lockout-as-DoS on anonymous-attack vectors\n * router.post(\"/auth/refresh-token\", refreshController, {\n * middleware: [loginThrottleMiddleware({ by: [\"ip\"], max: 10, window: \"1m\" })],\n * });\n */\nexport function loginThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\n const max = options.max ?? 5;\n const window = options.window ?? \"15m\";\n const lockoutDuration = options.lockoutDuration ?? \"15m\";\n const tracked = options.by ?? [\"email\", \"ip\"];\n const credentialField = options.identifierKey ?? \"email\";\n\n return async ({ request, response }) => {\n const identifiers = options.identify\n ? options.identify(request)\n : buildDefaultIdentifiers(request, tracked, credentialField);\n\n if (identifiers.length === 0) return;\n\n warnIfNullDriver();\n\n try {\n for (const identifier of identifiers) {\n const locked = isLocked(await cache.get(lockKey(identifier)));\n\n if (locked) {\n return response.tooManyRequests({\n error: options.errorMessage ?? t(\"auth.errors.tooManyAttempts\"),\n errorCode: AuthErrorCodes.TooManyAttempts,\n });\n }\n }\n } catch (error) {\n // Fail open — a cache outage (driver not initialised, network blip)\n // must never escalate into a full auth outage. Log and let the request\n // through; the onSent recorder below is independently guarded so an\n // outage there is also harmless.\n log.error(\"auth\", \"login-throttle\", error);\n\n return;\n }\n\n response.onSent(async (resolvedResponse: Response) => {\n try {\n const failed = options.isFailure\n ? options.isFailure(resolvedResponse)\n : !resolvedResponse.isOk;\n\n if (!failed) {\n for (const identifier of identifiers) {\n await cache.remove(counterKey(identifier));\n await cache.remove(lockKey(identifier));\n }\n\n return;\n }\n\n for (const identifier of identifiers) {\n const key = counterKey(identifier);\n\n // Open the window on the first failure: the create-only write means\n // only one of several racing servers sets the TTL, and an existing\n // window keeps its deadline (fixed window). The bump itself is an\n // atomic increment, so concurrent failures across servers are never\n // lost (redis INCRBY / pg upsert keep the window's TTL).\n await cache.set(key, 0, { ttl: window, onConflict: \"create\" });\n const count = await cache.increment(key);\n\n if (count >= max) {\n const lockSeconds = durationSeconds(lockoutDuration);\n\n // The lock stores its own deadline: a driver with sliding\n // expiration (`memoryExtended`) pushes the key's TTL forward on\n // every read, but the deadline in the value does not move, so a\n // client that keeps retrying is still released after\n // `lockoutDuration`.\n await cache.set(\n lockKey(identifier),\n lockSeconds === undefined ? true : Date.now() + lockSeconds * 1000,\n { ttl: lockoutDuration },\n );\n }\n }\n } catch (error) {\n log.error(\"auth\", \"login-throttle\", error);\n }\n });\n };\n}\n"],"mappings":";;;;;;;;;AA0FA,MAAM,iBAAiB;AACvB,MAAM,cAAc;AAEpB,MAAM,cAAc,eAA+B,GAAG,iBAAiB;AACvE,MAAM,WAAW,eAA+B,GAAG,cAAc;;;;;;AAOjE,SAAS,kBAAsC;CAC7C,IAAI;CAEJ,IAAI;EACF,SAAS,WAAW,YAAY,OAAO;CACzC,QAAQ;EACN;CACF;CAEA,OAAO,WAAW,UAAU,MAAM,CAAC,CAAC,OAAO,2BAA2B,CAAC,CAAC,OAAO;AACjF;;;;;;;;;;;;;;;AAgBA,SAAgB,uBAAuB,MAAc,OAAuB;CAC1E,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,KAAK,CAAC,CAAC,YAAY;CACvD,MAAM,MAAM,gBAAgB;CAE5B,OAAO,MACH,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,OAAO,KAAK,IACzD,WAAW,QAAQ,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,OAAO,KAAK;AAC1D;AAEA,IAAI,mBAAmB;;;;;AAMvB,SAAS,mBAAyB;CAChC,IAAI,kBAAkB;CAItB,IAFoB,MAAgD,eAAe,SAEhE,QAAQ;CAE3B,mBAAmB;CACnB,IAAI,KACF,QACA,kBACA,2IACF;AACF;;;;;AAMA,SAAS,SAAS,OAAyB;CACzC,IAAI,OAAO,UAAU,UACnB,OAAO,QAAQ,KAAK,IAAI;CAG1B,OAAO,QAAQ,KAAK;AACtB;;;;;AAMA,MAAM,UAAU;;;;;AAMhB,SAAS,gBAAgB,UAA+C;CACtE,IAAI,OAAO,aAAa,UACtB,OAAO,OAAO,SAAS,QAAQ,KAAK,WAAW,IAAI,WAAW;CAGhE,IAAI;CAEJ,IAAI;EACF,eAAe,QAAQ,QAAQ;CACjC,QAAQ;EACN,eAAe;CACjB;CAEA,OAAO,OAAO,iBAAiB,YAAY,OAAO,SAAS,YAAY,KAAK,eAAe,IACvF,eAAe,MACf;AACN;;;;;;;AAQA,SAAS,wBACP,SACA,SACA,iBACU;CACV,MAAM,cAAwB,CAAC;CAE/B,IAAI,QAAQ,SAAS,OAAO,GAAG;EAC7B,MAAM,QAAQ,QAAQ,MAAM,eAAe;EAE3C,IAAI,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,GACrD,YAAY,KAAK,SAAS,uBAAuB,SAAS,KAAK,GAAG;CAEtE;CAEA,IAAI,QAAQ,SAAS,IAAI,GAAG;EAC1B,MAAM,KAAK,QAAQ,SAAS;EAE5B,IAAI,IACF,YAAY,KAAK,MAAM,uBAAuB,MAAM,EAAE,GAAG;CAE7D;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,wBAAwB,UAAgC,CAAC,GAAe;CACtF,MAAM,MAAM,QAAQ,OAAO;CAC3B,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,kBAAkB,QAAQ,mBAAmB;CACnD,MAAM,UAAU,QAAQ,MAAM,CAAC,SAAS,IAAI;CAC5C,MAAM,kBAAkB,QAAQ,iBAAiB;CAEjD,OAAO,OAAO,EAAE,SAAS,eAAe;EACtC,MAAM,cAAc,QAAQ,WACxB,QAAQ,SAAS,OAAO,IACxB,wBAAwB,SAAS,SAAS,eAAe;EAE7D,IAAI,YAAY,WAAW,GAAG;EAE9B,iBAAiB;EAEjB,IAAI;GACF,KAAK,MAAM,cAAc,aAGvB,IAFe,SAAS,MAAM,MAAM,IAAI,QAAQ,UAAU,CAAC,CAElD,GACP,OAAO,SAAS,gBAAgB;IAC9B,OAAO,QAAQ,gBAAgB,EAAE,6BAA6B;IAC9D;GACF,CAAC;EAGP,SAAS,OAAO;GAKd,IAAI,MAAM,QAAQ,kBAAkB,KAAK;GAEzC;EACF;EAEA,SAAS,OAAO,OAAO,qBAA+B;GACpD,IAAI;IAKF,IAAI,EAJW,QAAQ,YACnB,QAAQ,UAAU,gBAAgB,IAClC,CAAC,iBAAiB,OAET;KACX,KAAK,MAAM,cAAc,aAAa;MACpC,MAAM,MAAM,OAAO,WAAW,UAAU,CAAC;MACzC,MAAM,MAAM,OAAO,QAAQ,UAAU,CAAC;KACxC;KAEA;IACF;IAEA,KAAK,MAAM,cAAc,aAAa;KACpC,MAAM,MAAM,WAAW,UAAU;KAOjC,MAAM,MAAM,IAAI,KAAK,GAAG;MAAE,KAAK;MAAQ,YAAY;KAAS,CAAC;KAG7D,IAAI,MAFgB,MAAM,UAAU,GAAG,KAE1B,KAAK;MAChB,MAAM,cAAc,gBAAgB,eAAe;MAOnD,MAAM,MAAM,IACV,QAAQ,UAAU,GAClB,gBAAgB,SAAY,OAAO,KAAK,IAAI,IAAI,cAAc,KAC9D,EAAE,KAAK,gBAAgB,CACzB;KACF;IACF;GACF,SAAS,OAAO;IACd,IAAI,MAAM,QAAQ,kBAAkB,KAAK;GAC3C;EACF,CAAC;CACH;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LoginThrottleOptions } from "./login-throttle.middleware.mjs";
|
|
2
2
|
import { Middleware } from "@warlock.js/core";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/middleware/one-time-token-throttle.middleware.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Throttle for routes that ISSUE a verification or reset token ("send
|
|
7
7
|
* verification email", "forgot password"). Built on
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"one-time-token-throttle.middleware.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"one-time-token-throttle.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/one-time-token-throttle.middleware.ts"],"mappings":";;;;;;AAiBA;;;;;;;;AAA4F;AAwB5F;;;iBAxBgB,4BAAA,CAA6B,OAAA,GAAS,oBAAA,GAA4B,UAAU;;;;;AAwBE;;;;;;;;;iBAA9E,8BAAA,CAA+B,OAAA,GAAS,oBAAA,GAA4B,UAAU"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loginThrottleMiddleware } from "./login-throttle.middleware.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/auth/src/middleware/one-time-token-throttle.middleware.ts
|
|
4
4
|
/**
|
|
5
5
|
* Throttle for routes that ISSUE a verification or reset token ("send
|
|
6
6
|
* verification email", "forgot password"). Built on
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"one-time-token-throttle.middleware.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"one-time-token-throttle.middleware.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/one-time-token-throttle.middleware.ts"],"sourcesContent":["import type { Middleware } from \"@warlock.js/core\";\nimport { loginThrottleMiddleware, type LoginThrottleOptions } from \"./login-throttle.middleware\";\n\n/**\n * Throttle for routes that ISSUE a verification or reset token (\"send\n * verification email\", \"forgot password\"). Built on\n * {@link loginThrottleMiddleware}, tracked per email and per IP, with one\n * difference: EVERY request counts. A reset request answers the same success\n * for known and unknown emails, so a failure-aware counter would never trip.\n *\n * Defaults: 3 requests per 1h window, then a 1h lockout (`429`, `EC004`).\n *\n * @example\n * router.post(\"/auth/forgot-password\", forgotPassword, {\n * middleware: [tokenIssueThrottleMiddleware()],\n * });\n */\nexport function tokenIssueThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\n return loginThrottleMiddleware({\n max: 3,\n window: \"1h\",\n lockoutDuration: \"1h\",\n by: [\"email\", \"ip\"],\n isFailure: () => true,\n ...options,\n });\n}\n\n/**\n * Throttle for routes that CONSUME a token (\"verify email\", \"reset\n * password\"). Built on {@link loginThrottleMiddleware}: per IP (the body\n * carries a token, not an account), failure-aware — a rejected token counts,\n * a successful use clears the counter.\n *\n * Defaults: 10 failures per 15m window, then a 15m lockout (`429`, `EC004`).\n *\n * @example\n * router.post(\"/auth/reset-password\", resetPasswordController, {\n * middleware: [tokenConsumeThrottleMiddleware()],\n * });\n */\nexport function tokenConsumeThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\n return loginThrottleMiddleware({\n max: 10,\n window: \"15m\",\n lockoutDuration: \"15m\",\n by: [\"ip\"],\n ...options,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,SAAgB,6BAA6B,UAAgC,CAAC,GAAe;CAC3F,OAAO,wBAAwB;EAC7B,KAAK;EACL,QAAQ;EACR,iBAAiB;EACjB,IAAI,CAAC,SAAS,IAAI;EAClB,iBAAiB;EACjB,GAAG;CACL,CAAC;AACH;;;;;;;;;;;;;;AAeA,SAAgB,+BAA+B,UAAgC,CAAC,GAAe;CAC7F,OAAO,wBAAwB;EAC7B,KAAK;EACL,QAAQ;EACR,iBAAiB;EACjB,IAAI,CAAC,IAAI;EACT,GAAG;CACL,CAAC;AACH"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LoginThrottleOptions } from "./login-throttle.middleware.mjs";
|
|
2
2
|
import { Middleware } from "@warlock.js/core";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/middleware/otp-throttle.middleware.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Throttle for the route that SENDS a login code. {@link tokenIssueThrottleMiddleware}
|
|
7
7
|
* keyed on the `phone` input and IP: every request counts (the answer is the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otp-throttle.middleware.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"otp-throttle.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/otp-throttle.middleware.ts"],"mappings":";;;;;;AAcA;;;;;;;;iBAAgB,4BAAA,CAA6B,OAAA,GAAS,oBAAA,GAA4B,UAAU;AAe5F;;;;;;;;AAA2F;;;AAA3F,iBAAgB,2BAAA,CAA4B,OAAA,GAAS,oBAAA,GAA4B,UAAU"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loginThrottleMiddleware } from "./login-throttle.middleware.mjs";
|
|
2
2
|
import { tokenIssueThrottleMiddleware } from "./one-time-token-throttle.middleware.mjs";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/middleware/otp-throttle.middleware.ts
|
|
5
5
|
/**
|
|
6
6
|
* Throttle for the route that SENDS a login code. {@link tokenIssueThrottleMiddleware}
|
|
7
7
|
* keyed on the `phone` input and IP: every request counts (the answer is the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otp-throttle.middleware.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"otp-throttle.middleware.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/otp-throttle.middleware.ts"],"sourcesContent":["import type { Middleware } from \"@warlock.js/core\";\nimport { loginThrottleMiddleware, type LoginThrottleOptions } from \"./login-throttle.middleware\";\nimport { tokenIssueThrottleMiddleware } from \"./one-time-token-throttle.middleware\";\n\n/**\n * Throttle for the route that SENDS a login code. {@link tokenIssueThrottleMiddleware}\n * keyed on the `phone` input and IP: every request counts (the answer is the\n * same for unknown phones), 3 per 1h, then a 1h lockout (`429`, `EC004`).\n *\n * @example\n * router.post(\"/auth/otp/request\", requestOtpController, {\n * middleware: [otpRequestThrottleMiddleware()],\n * });\n */\nexport function otpRequestThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\n return tokenIssueThrottleMiddleware({ identifierKey: \"phone\", ...options });\n}\n\n/**\n * Throttle for the route that VERIFIES a login code. {@link loginThrottleMiddleware}\n * keyed on `phone` and IP, failure-aware: 5 failures per 15m, then a 15m lockout\n * (`429`, `EC004`). Complements the per-code attempt cap, which a fresh code\n * would otherwise reset.\n *\n * @example\n * router.post(\"/auth/otp/verify\", verifyOtpController, {\n * middleware: [otpVerifyThrottleMiddleware()],\n * });\n */\nexport function otpVerifyThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\n return loginThrottleMiddleware({\n max: 5,\n window: \"15m\",\n lockoutDuration: \"15m\",\n by: [\"email\", \"ip\"],\n identifierKey: \"phone\",\n ...options,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,6BAA6B,UAAgC,CAAC,GAAe;CAC3F,OAAO,6BAA6B;EAAE,eAAe;EAAS,GAAG;CAAQ,CAAC;AAC5E;;;;;;;;;;;;AAaA,SAAgB,4BAA4B,UAAgC,CAAC,GAAe;CAC1F,OAAO,wBAAwB;EAC7B,KAAK;EACL,QAAQ;EACR,iBAAiB;EACjB,IAAI,CAAC,SAAS,IAAI;EAClB,eAAe;EACf,GAAG;CACL,CAAC;AACH"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Middleware } from "@warlock.js/core";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/auth/src/middleware/require-verified-email.middleware.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Opt-in route gate that rejects a user who has not verified their email with
|
|
6
6
|
* {@link EmailNotVerifiedError} (`403`, `EC007`). Place it AFTER
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"require-verified-email.middleware.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"require-verified-email.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/require-verified-email.middleware.ts"],"mappings":";;;;;AAeA;;;;AAAkD;;;;;iBAAlC,oBAAA,IAAwB,UAAU"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { authConfig } from "../services/auth-config.mjs";
|
|
2
2
|
import { EmailNotVerifiedError } from "../errors/email-not-verified.error.mjs";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/middleware/require-verified-email.middleware.ts
|
|
5
5
|
/**
|
|
6
6
|
* Opt-in route gate that rejects a user who has not verified their email with
|
|
7
7
|
* {@link EmailNotVerifiedError} (`403`, `EC007`). Place it AFTER
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"require-verified-email.middleware.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"require-verified-email.middleware.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/require-verified-email.middleware.ts"],"sourcesContent":["import type { Middleware } from \"@warlock.js/core\";\nimport { EmailNotVerifiedError } from \"../errors/email-not-verified.error\";\nimport { authConfig } from \"../services/auth-config\";\n\n/**\n * Opt-in route gate that rejects a user who has not verified their email with\n * {@link EmailNotVerifiedError} (`403`, `EC007`). Place it AFTER\n * `authMiddleware` — it reads `request.locals.user`. A request with no user\n * fails closed with the same error.\n *\n * @example\n * router.post(\"/orders\", createOrder, {\n * middleware: [authMiddleware(\"user\"), requireVerifiedEmail()],\n * });\n */\nexport function requireVerifiedEmail(): Middleware {\n return async ({ request }) => {\n const user = request.locals.user as { get?: (key: string) => unknown } | undefined;\n\n if (!user?.get?.(authConfig.verification.field())) {\n throw new EmailNotVerifiedError();\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,uBAAmC;CACjD,OAAO,OAAO,EAAE,cAAc;EAG5B,IAAI,CAFS,QAAQ,OAAO,MAEjB,MAAM,WAAW,aAAa,MAAM,CAAC,GAC9C,MAAM,IAAI,sBAAsB;CAEpC;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Auth } from "../auth.model.mjs";
|
|
2
2
|
import { Model } from "@warlock.js/cascade";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/models/access-token/access-token.model.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Seal schema for the persisted access-token record. Exported so an override
|
|
7
7
|
* can spread it and add columns (e.g. a tenant key) without re-declaring the
|
|
@@ -18,23 +18,19 @@ declare const accessTokenSchema: import("@warlock.js/seal").ObjectValidator<{
|
|
|
18
18
|
isOptional: true;
|
|
19
19
|
};
|
|
20
20
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
21
|
-
}> & import("@warlock.js/seal").StandardSchemaV1<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
isOptional: true;
|
|
35
|
-
};
|
|
36
|
-
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
37
|
-
}>>;
|
|
21
|
+
}> & import("@warlock.js/seal").StandardSchemaV1<{
|
|
22
|
+
family_id?: string | undefined;
|
|
23
|
+
token: string;
|
|
24
|
+
user_id: string | number | boolean;
|
|
25
|
+
user_type: string;
|
|
26
|
+
expires_at: Date;
|
|
27
|
+
}, {
|
|
28
|
+
family_id?: string | undefined;
|
|
29
|
+
token: string;
|
|
30
|
+
user_id: string | number | boolean;
|
|
31
|
+
user_type: string;
|
|
32
|
+
expires_at: Date;
|
|
33
|
+
}>;
|
|
38
34
|
/**
|
|
39
35
|
* Persisted access-token record + the data layer for access tokens.
|
|
40
36
|
*
|
|
@@ -55,23 +51,19 @@ declare class AccessToken extends Model {
|
|
|
55
51
|
isOptional: true;
|
|
56
52
|
};
|
|
57
53
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
58
|
-
}> & import("@warlock.js/seal").StandardSchemaV1<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
isOptional: true;
|
|
72
|
-
};
|
|
73
|
-
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
74
|
-
}>>;
|
|
54
|
+
}> & import("@warlock.js/seal").StandardSchemaV1<{
|
|
55
|
+
family_id?: string | undefined;
|
|
56
|
+
token: string;
|
|
57
|
+
user_id: string | number | boolean;
|
|
58
|
+
user_type: string;
|
|
59
|
+
expires_at: Date;
|
|
60
|
+
}, {
|
|
61
|
+
family_id?: string | undefined;
|
|
62
|
+
token: string;
|
|
63
|
+
user_id: string | number | boolean;
|
|
64
|
+
user_type: string;
|
|
65
|
+
expires_at: Date;
|
|
66
|
+
}>;
|
|
75
67
|
/**
|
|
76
68
|
* The raw signed JWT is a bearer credential — never serialize it. `hidden`
|
|
77
69
|
* only strips it from `toJSON()` / `JSON.stringify(model)` (so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-token.model.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access-token.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/access-token/access-token.model.ts"],"mappings":";;;;;;AAaA;;;;;;cAAa,iBAAA,6BAAiB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmBjB,WAAA,SAAoB,KAAA;EAAA,OACjB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;yHAHG;;;;;;;;;;;;;;;;;;EAiHa;;;;;;EAAA,OAtGtB,MAAA;EAXsB;;;;;;;EAAA,OAoBtB,cAAA;;MAGH,MAAA;;MAKA,QAAA;;;;;;;;;;;;;;;;MAmBA,SAAA;;;;;;;MAcA,YAAA;;;;SAOG,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,SAAA,EAAW,IAAA,EAAM,QAAA,YAAiB,OAAA,CAAA,WAAA;EAxCtE;;;EAAA,OAqDG,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,WAAA;EAbxB;;;EAAA,OAoBZ,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;EApBN;;;EAAA,OA2BjC,gBAAA,CAAiB,IAAA,EAAM,IAAA,GAAI,OAAA;EAd3B;;;;;EAAA,OAuBM,4BAAA,CAA6B,IAAA,EAAM,IAAA,EAAM,QAAA,WAAmB,OAAA;EAhBpD;;;;EAAA,OAyBR,YAAA,IAAgB,OAAA;EAlBL;;;;;;;;;;;EAAA,OAuCX,iBAAA,IAAqB,OAAA,CAAQ,WAAA;EAW7B;;;;AAAyC;EAAzC,OAAA,kBAAA,IAAsB,OAAA,CAAQ,WAAA;AAAA"}
|
|
@@ -2,7 +2,7 @@ import { isNeverExpiring, isUsableExpiry } from "../../utils/token-expiry.mjs";
|
|
|
2
2
|
import { Model } from "@warlock.js/cascade";
|
|
3
3
|
import { v } from "@warlock.js/seal";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/auth/src/models/access-token/access-token.model.ts
|
|
6
6
|
/**
|
|
7
7
|
* Seal schema for the persisted access-token record. Exported so an override
|
|
8
8
|
* can spread it and add columns (e.g. a tenant key) without re-declaring the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-token.model.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access-token.model.mjs","names":[],"sources":["../../../../../../../../../packages/auth/src/models/access-token/access-token.model.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\nimport { v } from \"@warlock.js/seal\";\nimport { isNeverExpiring, isUsableExpiry } from \"../../utils/token-expiry\";\nimport type { Auth } from \"../auth.model\";\n\n/**\n * Seal schema for the persisted access-token record. Exported so an override\n * can spread it and add columns (e.g. a tenant key) without re-declaring the\n * base shape.\n *\n * `last_access` and the `is_active` soft-revoke flag were removed — neither was\n * ever read; access tokens are revoked by deleting the row.\n */\nexport const accessTokenSchema = v.object({\n token: v.string().required(),\n user_id: v.scalar().required(),\n user_type: v.string().required(),\n /** Null for tokens issued before family tracking was introduced. */\n family_id: v.string().optional(),\n expires_at: v.date().required(),\n});\n\n/**\n * Persisted access-token record + the data layer for access tokens.\n *\n * **Role.** Owns access-token persistence and lookup. The middleware checks a\n * presented JWT against this table so deleting a row (logout) invalidates the\n * token immediately, before its JWT expiry. The auth service goes through the\n * named statics exclusively, so it never hard-codes a column name and an\n * override can rename/add columns by registering under\n * `config.auth.accessToken.model`.\n */\nexport class AccessToken extends Model {\n public static table = \"access_tokens\";\n\n public static schema = accessTokenSchema;\n\n /**\n * The raw signed JWT is a bearer credential — never serialize it. `hidden`\n * only strips it from `toJSON()` / `JSON.stringify(model)` (so\n * `response.json(token)` or a resource cannot leak it); `get(\"token\")`,\n * `findByToken` and every query still read the column.\n */\n public static hidden = [\"token\"];\n\n /**\n * Permanent regardless of the data source default — Mongo's driver default\n * is `\"trash\"`, which would copy live credential material (the JWT itself)\n * into `access_tokensTrash` on every `destroy()` (logout, expiry purge,\n * never-expiring purge). A revoked/expired access token must not survive\n * anywhere.\n */\n public static deleteStrategy = \"permanent\" as const;\n\n /** The user this token was issued for. */\n public get userId() {\n return this.get(\"user_id\");\n }\n\n /** The user-type slug this token was issued for. */\n public get userType(): string {\n return this.get(\"user_type\");\n }\n\n /**\n * Whether the persisted expiry has passed.\n *\n * The middleware asks this on every request, which is what makes the row the\n * authority it always looked like it was: before 4.12.0 the gate only checked\n * that the row *existed*, so a row the database knew was dead still let its\n * token through — the database was never asked.\n *\n * **Fails closed.** A missing or unparseable `expires_at` counts as expired.\n * `expires_at` is `required` in the schema, so a row that cannot answer \"when\n * does this die\" is malformed, and the safe reading of a malformed credential\n * is that it is not one. This is deliberately stricter than a naive\n * `now > expires_at`, which answers `false` for an `Invalid Date` and thereby\n * grants exactly the poisoned rows an unlimited life.\n */\n public get isExpired(): boolean {\n const expiresAt = this.get(\"expires_at\");\n\n if (!isUsableExpiry(expiresAt)) return true;\n\n return new Date(expiresAt).getTime() <= Date.now();\n }\n\n /**\n * Whether nothing about this row can ever retire it — an unusable\n * `expires_at`, or a token carrying no `exp` claim. See\n * {@link isNeverExpiring}; this is the predicate `purgeNeverExpiring` selects\n * on.\n */\n public get neverExpires(): boolean {\n return isNeverExpiring(this.get(\"token\"), this.get(\"expires_at\"));\n }\n\n /**\n * Persist a freshly-signed access token for the user.\n */\n public static issue(user: Auth, token: string, expiresAt: Date, familyId?: string) {\n return this.create({\n token,\n user_id: user.id,\n user_type: user.userType,\n family_id: familyId,\n expires_at: expiresAt,\n });\n }\n\n /**\n * Find an access-token row by its raw token string.\n */\n public static findByToken(token: string): Promise<AccessToken | null> {\n return this.first({ token });\n }\n\n /**\n * Delete a specific token that belongs to the given user.\n */\n public static deleteForUser(user: Auth, token: string) {\n return this.delete({ token, user_id: user.id });\n }\n\n /**\n * Delete every access token belonging to the user.\n */\n public static deleteAllForUser(user: Auth) {\n return this.delete({ user_id: user.id });\n }\n\n /**\n * Delete every access token attached to one family, plus pre-family rows for\n * the same user type. The latter is deliberately conservative during the\n * additive upgrade: old access rows cannot be assigned safely to a family.\n */\n public static async deleteFamilyAndLegacyForUser(user: Auth, familyId: string): Promise<void> {\n await this.delete({ user_id: user.id, user_type: user.userType, family_id: familyId });\n await this.delete({ user_id: user.id, user_type: user.userType, family_id: null });\n }\n\n /**\n * Hard-delete every expired access-token row. Returns the number removed.\n * Runs from the `auth.cleanup` CLI command (a cold batch path).\n */\n public static async purgeExpired(): Promise<number> {\n const expiredTokens = await this.query().where(\"expires_at\", \"<\", new Date()).get();\n\n for (const token of expiredTokens) {\n await token.destroy();\n }\n\n return expiredTokens.length;\n }\n\n /**\n * Every row that can never retire itself — see {@link neverExpires}.\n *\n * **A full scan, filtered in memory, on purpose.** The defining case is a row\n * whose `expires_at` is an `Invalid Date`, which no date predicate can select\n * (`< now` and `> now` are both `false` for it), and the definitive case is a\n * token with no `exp` claim, which lives inside the token string rather than\n * in a column. Neither is expressible as a `where`, so the rows have to be\n * read to be judged. This runs from a one-off remediation command, not a\n * request path.\n */\n public static async findNeverExpiring(): Promise<AccessToken[]> {\n const tokens = await this.query().get();\n\n return tokens.filter((token: AccessToken) => token.neverExpires);\n }\n\n /**\n * Hard-delete every never-expiring row, returning the rows removed so the\n * caller can report what it revoked. Deletion *is* revocation for access\n * tokens — the middleware rejects a token with no row.\n */\n public static async purgeNeverExpiring(): Promise<AccessToken[]> {\n const tokens = await this.findNeverExpiring();\n\n for (const token of tokens) {\n await token.destroy();\n }\n\n return tokens;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAa,oBAAoB,EAAE,OAAO;CACxC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;;CAE/B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,YAAY,EAAE,KAAK,CAAC,CAAC,SAAS;AAChC,CAAC;;;;;;;;;;;AAYD,IAAa,cAAb,cAAiC,MAAM;;eACf;;;gBAEC;;;gBAQA,CAAC,OAAO;;;wBASA;;;CAG/B,IAAW,SAAS;EAClB,OAAO,KAAK,IAAI,SAAS;CAC3B;;CAGA,IAAW,WAAmB;EAC5B,OAAO,KAAK,IAAI,WAAW;CAC7B;;;;;;;;;;;;;;;;CAiBA,IAAW,YAAqB;EAC9B,MAAM,YAAY,KAAK,IAAI,YAAY;EAEvC,IAAI,CAAC,eAAe,SAAS,GAAG,OAAO;EAEvC,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,QAAQ,KAAK,KAAK,IAAI;CACnD;;;;;;;CAQA,IAAW,eAAwB;EACjC,OAAO,gBAAgB,KAAK,IAAI,OAAO,GAAG,KAAK,IAAI,YAAY,CAAC;CAClE;;;;CAKA,OAAc,MAAM,MAAY,OAAe,WAAiB,UAAmB;EACjF,OAAO,KAAK,OAAO;GACjB;GACA,SAAS,KAAK;GACd,WAAW,KAAK;GAChB,WAAW;GACX,YAAY;EACd,CAAC;CACH;;;;CAKA,OAAc,YAAY,OAA4C;EACpE,OAAO,KAAK,MAAM,EAAE,MAAM,CAAC;CAC7B;;;;CAKA,OAAc,cAAc,MAAY,OAAe;EACrD,OAAO,KAAK,OAAO;GAAE;GAAO,SAAS,KAAK;EAAG,CAAC;CAChD;;;;CAKA,OAAc,iBAAiB,MAAY;EACzC,OAAO,KAAK,OAAO,EAAE,SAAS,KAAK,GAAG,CAAC;CACzC;;;;;;CAOA,aAAoB,6BAA6B,MAAY,UAAiC;EAC5F,MAAM,KAAK,OAAO;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU,WAAW;EAAS,CAAC;EACrF,MAAM,KAAK,OAAO;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU,WAAW;EAAK,CAAC;CACnF;;;;;CAMA,aAAoB,eAAgC;EAClD,MAAM,gBAAgB,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,cAAc,qBAAK,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI;EAElF,KAAK,MAAM,SAAS,eAClB,MAAM,MAAM,QAAQ;EAGtB,OAAO,cAAc;CACvB;;;;;;;;;;;;CAaA,aAAoB,oBAA4C;EAG9D,QAAO,MAFc,KAAK,MAAM,CAAC,CAAC,IAAI,EAEzB,CAAC,QAAQ,UAAuB,MAAM,YAAY;CACjE;;;;;;CAOA,aAAoB,qBAA6C;EAC/D,MAAM,SAAS,MAAM,KAAK,kBAAkB;EAE5C,KAAK,MAAM,SAAS,QAClB,MAAM,MAAM,QAAQ;EAGtB,OAAO;CACT;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AccessToken } from "./access-token.model.mjs";
|
|
2
2
|
import { migrate } from "@warlock.js/cascade";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/models/access-token/family-association.migration.ts
|
|
5
5
|
/** Adds an optional family association without invalidating existing access tokens. */
|
|
6
6
|
const AccessTokenFamilyAssociationMigration = migrate(AccessToken, {
|
|
7
7
|
name: "accessTokenFamilyAssociation",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"family-association.migration.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"family-association.migration.mjs","names":[],"sources":["../../../../../../../../../packages/auth/src/models/access-token/family-association.migration.ts"],"sourcesContent":["import { migrate } from \"@warlock.js/cascade\";\nimport { AccessToken } from \"./access-token.model\";\n\n/** Adds an optional family association without invalidating existing access tokens. */\nexport const AccessTokenFamilyAssociationMigration = migrate(AccessToken, {\n name: \"accessTokenFamilyAssociation\",\n up() {\n this.string(\"family_id\", 64).nullable().index();\n },\n down() {\n this.dropColumn(\"family_id\");\n },\n});\n"],"mappings":";;;;;AAIA,MAAa,wCAAwC,QAAQ,aAAa;CACxE,MAAM;CACN,KAAK;EACH,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM;CAChD;CACA,OAAO;EACL,KAAK,WAAW,WAAW;CAC7B;AACF,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AccessToken } from "./access-token.model.mjs";
|
|
2
2
|
import { migrate } from "@warlock.js/cascade";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/models/access-token/migration.ts
|
|
5
5
|
const AccessTokenMigration = migrate(AccessToken, {
|
|
6
6
|
name: "accessToken",
|
|
7
7
|
up() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"migration.mjs","names":[],"sources":["../../../../../../../../../packages/auth/src/models/access-token/migration.ts"],"sourcesContent":["import { migrate } from \"@warlock.js/cascade\";\nimport { AccessToken } from \"./access-token.model\";\n\nexport const AccessTokenMigration = migrate(AccessToken, {\n name: \"accessToken\",\n up() {\n // Create table\n this.createTableIfNotExists();\n\n // Primary key\n this.primaryUuid();\n\n // Token field\n this.text(\"token\").unique();\n\n // User reference (flat columns for cross-driver compatibility).\n // `foreignId` matches the DataSource's default primary-key type, so the\n // column stays compatible with the user model's `id` whether the app uses\n // integer (the default) or uuid PKs — hardcoding uuid here 500'd every\n // login on a default integer-PK app (finding 5e47bdb3).\n this.foreignId(\"user_id\").index();\n this.string(\"user_type\", 50).nullable();\n\n // Expiry — enables server-side cleanup of stale access-token rows\n this.timestamp(\"expires_at\").index().nullable();\n\n // Timestamps\n this.timestamps();\n },\n down() {\n this.dropTableIfExists();\n },\n});\n"],"mappings":";;;;AAGA,MAAa,uBAAuB,QAAQ,aAAa;CACvD,MAAM;CACN,KAAK;EAEH,KAAK,uBAAuB;EAG5B,KAAK,YAAY;EAGjB,KAAK,KAAK,OAAO,CAAC,CAAC,OAAO;EAO1B,KAAK,UAAU,SAAS,CAAC,CAAC,MAAM;EAChC,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,SAAS;EAGtC,KAAK,UAAU,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS;EAG9C,KAAK,WAAW;CAClB;CACA,OAAO;EACL,KAAK,kBAAkB;CACzB;AACF,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Auth } from "../auth.model.mjs";
|
|
2
2
|
import { Model } from "@warlock.js/cascade";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/models/auth-token-family/auth-token-family.model.d.ts
|
|
5
5
|
/** Durable revision authority for one refresh-token rotation family. */
|
|
6
6
|
declare const authTokenFamilySchema: import("@warlock.js/seal").ObjectValidator<{
|
|
7
7
|
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
@@ -13,27 +13,19 @@ declare const authTokenFamilySchema: import("@warlock.js/seal").ObjectValidator<
|
|
|
13
13
|
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
14
14
|
isOptional: true;
|
|
15
15
|
};
|
|
16
|
-
}> & import("@warlock.js/seal").StandardSchemaV1<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
30
|
-
revision: import("@warlock.js/seal").NumberValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
|
|
31
|
-
hasDefault: true;
|
|
32
|
-
};
|
|
33
|
-
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
34
|
-
isOptional: true;
|
|
35
|
-
};
|
|
36
|
-
}>>;
|
|
16
|
+
}> & import("@warlock.js/seal").StandardSchemaV1<{
|
|
17
|
+
revision?: number | undefined;
|
|
18
|
+
revoked_at?: Date | undefined;
|
|
19
|
+
family_id: string;
|
|
20
|
+
user_id: string | number | boolean;
|
|
21
|
+
user_type: string;
|
|
22
|
+
}, {
|
|
23
|
+
revision?: number | undefined;
|
|
24
|
+
revoked_at?: Date | undefined;
|
|
25
|
+
family_id: string;
|
|
26
|
+
user_id: string | number | boolean;
|
|
27
|
+
user_type: string;
|
|
28
|
+
}>;
|
|
37
29
|
/** Coordinates family-wide refresh, replay containment, and logout transitions. */
|
|
38
30
|
declare class AuthTokenFamily extends Model {
|
|
39
31
|
static table: string;
|
|
@@ -47,27 +39,19 @@ declare class AuthTokenFamily extends Model {
|
|
|
47
39
|
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
48
40
|
isOptional: true;
|
|
49
41
|
};
|
|
50
|
-
}> & import("@warlock.js/seal").StandardSchemaV1<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
64
|
-
revision: import("@warlock.js/seal").NumberValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
|
|
65
|
-
hasDefault: true;
|
|
66
|
-
};
|
|
67
|
-
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
68
|
-
isOptional: true;
|
|
69
|
-
};
|
|
70
|
-
}>>;
|
|
42
|
+
}> & import("@warlock.js/seal").StandardSchemaV1<{
|
|
43
|
+
revision?: number | undefined;
|
|
44
|
+
revoked_at?: Date | undefined;
|
|
45
|
+
family_id: string;
|
|
46
|
+
user_id: string | number | boolean;
|
|
47
|
+
user_type: string;
|
|
48
|
+
}, {
|
|
49
|
+
revision?: number | undefined;
|
|
50
|
+
revoked_at?: Date | undefined;
|
|
51
|
+
family_id: string;
|
|
52
|
+
user_id: string | number | boolean;
|
|
53
|
+
user_type: string;
|
|
54
|
+
}>;
|
|
71
55
|
/** Whether a family-wide revoke has made every member unusable. */
|
|
72
56
|
get isRevoked(): boolean;
|
|
73
57
|
/** Create the durable family record before its first token pair is issued. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-token-family.model.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"auth-token-family.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/auth-token-family/auth-token-family.model.ts"],"mappings":";;;;;cAKa,qBAAA,6BAAqB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;cASrB,eAAA,SAAwB,KAAA;EAAA,OACrB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;MAGT,SAAA;;SAKG,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,QAAA,WAAgB,OAAA,CAAA,eAAA;EAAA,OASlC,cAAA,CAAe,QAAA,WAAmB,OAAA,CAAQ,eAAA;EATR;EAAA,OAc5B,MAAA,CAAO,IAAA,EAAM,IAAA,EAAM,QAAA,WAAmB,OAAA,CAAQ,eAAA;EALlB;EAAA,OAqClC,SAAA,CAAU,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,eAAA;EAhCqB;;;;EAAA,OA0CpD,eAAA,CAAgB,QAAA,UAAkB,gBAAA,WAA2B,OAAA;EAAA;EAAA,OAQ7D,MAAA,CAAO,QAAA,UAAkB,gBAAA,WAA2B,OAAA;AAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Model } from "@warlock.js/cascade";
|
|
2
2
|
import { v } from "@warlock.js/seal";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/models/auth-token-family/auth-token-family.model.ts
|
|
5
5
|
/** Durable revision authority for one refresh-token rotation family. */
|
|
6
6
|
const authTokenFamilySchema = v.object({
|
|
7
7
|
family_id: v.string().required(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-token-family.model.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"auth-token-family.model.mjs","names":[],"sources":["../../../../../../../../../packages/auth/src/models/auth-token-family/auth-token-family.model.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\nimport { v } from \"@warlock.js/seal\";\nimport type { Auth } from \"../auth.model\";\n\n/** Durable revision authority for one refresh-token rotation family. */\nexport const authTokenFamilySchema = v.object({\n family_id: v.string().required(),\n user_id: v.scalar().required(),\n user_type: v.string().required(),\n revision: v.number().default(0),\n revoked_at: v.date().optional(),\n});\n\n/** Coordinates family-wide refresh, replay containment, and logout transitions. */\nexport class AuthTokenFamily extends Model {\n public static table = \"auth_token_families\";\n\n public static schema = authTokenFamilySchema;\n\n /** Whether a family-wide revoke has made every member unusable. */\n public get isRevoked(): boolean {\n return Boolean(this.get(\"revoked_at\"));\n }\n\n /** Create the durable family record before its first token pair is issued. */\n public static issue(user: Auth, familyId: string) {\n return this.create({\n family_id: familyId,\n user_id: user.id,\n user_type: user.userType,\n revision: 0,\n });\n }\n\n public static findByFamilyId(familyId: string): Promise<AuthTokenFamily | null> {\n return this.first({ family_id: familyId });\n }\n\n /** Add a durable row for an existing lineage through a driver-native upsert. */\n public static async ensure(user: Auth, familyId: string): Promise<AuthTokenFamily> {\n const existing = await this.findByFamilyId(familyId);\n\n if (existing) {\n if (\n existing.get(\"user_id\") !== user.id ||\n existing.get<string>(\"user_type\") !== user.userType\n ) {\n throw new Error(\"Token family belongs to a different user\");\n }\n\n return existing;\n }\n\n await this.atomic(\n { family_id: familyId },\n { $setOnInsert: { user_id: user.id, user_type: user.userType, revision: 0 } },\n { upsert: true },\n );\n\n const family = await this.findByFamilyId(familyId);\n\n if (!family) throw new Error(\"Token family upsert did not return a family\");\n\n if (family.get(\"user_id\") !== user.id || family.get<string>(\"user_type\") !== user.userType) {\n throw new Error(\"Token family belongs to a different user\");\n }\n\n return family;\n }\n\n /** Every non-revoked family for a user, including a rotated refresh row. */\n public static activeFor(user: Auth): Promise<AuthTokenFamily[]> {\n return this.query()\n .where({ user_id: user.id, user_type: user.userType, revoked_at: null })\n .get();\n }\n\n /**\n * Compare-and-set the family revision. A zero result is a concurrent family\n * transition, never a successful no-op.\n */\n public static advanceRevision(familyId: string, expectedRevision: number): Promise<number> {\n return this.atomic(\n { family_id: familyId, revision: expectedRevision, revoked_at: null },\n { $inc: { revision: 1 } },\n );\n }\n\n /** Stamp the durable family revoke marker after a successful CAS. */\n public static revoke(familyId: string, expectedRevision: number): Promise<number> {\n return this.atomic(\n { family_id: familyId, revision: expectedRevision + 1, revoked_at: null },\n { $set: { revoked_at: new Date() } },\n );\n }\n}\n"],"mappings":";;;;;AAKA,MAAa,wBAAwB,EAAE,OAAO;CAC5C,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,UAAU,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC;CAC9B,YAAY,EAAE,KAAK,CAAC,CAAC,SAAS;AAChC,CAAC;;AAGD,IAAa,kBAAb,cAAqC,MAAM;;eACnB;;;gBAEC;;;CAGvB,IAAW,YAAqB;EAC9B,OAAO,QAAQ,KAAK,IAAI,YAAY,CAAC;CACvC;;CAGA,OAAc,MAAM,MAAY,UAAkB;EAChD,OAAO,KAAK,OAAO;GACjB,WAAW;GACX,SAAS,KAAK;GACd,WAAW,KAAK;GAChB,UAAU;EACZ,CAAC;CACH;CAEA,OAAc,eAAe,UAAmD;EAC9E,OAAO,KAAK,MAAM,EAAE,WAAW,SAAS,CAAC;CAC3C;;CAGA,aAAoB,OAAO,MAAY,UAA4C;EACjF,MAAM,WAAW,MAAM,KAAK,eAAe,QAAQ;EAEnD,IAAI,UAAU;GACZ,IACE,SAAS,IAAI,SAAS,MAAM,KAAK,MACjC,SAAS,IAAY,WAAW,MAAM,KAAK,UAE3C,MAAM,IAAI,MAAM,0CAA0C;GAG5D,OAAO;EACT;EAEA,MAAM,KAAK,OACT,EAAE,WAAW,SAAS,GACtB,EAAE,cAAc;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU,UAAU;EAAE,EAAE,GAC5E,EAAE,QAAQ,KAAK,CACjB;EAEA,MAAM,SAAS,MAAM,KAAK,eAAe,QAAQ;EAEjD,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,6CAA6C;EAE1E,IAAI,OAAO,IAAI,SAAS,MAAM,KAAK,MAAM,OAAO,IAAY,WAAW,MAAM,KAAK,UAChF,MAAM,IAAI,MAAM,0CAA0C;EAG5D,OAAO;CACT;;CAGA,OAAc,UAAU,MAAwC;EAC9D,OAAO,KAAK,MAAM,CAAC,CAChB,MAAM;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU,YAAY;EAAK,CAAC,CAAC,CACvE,IAAI;CACT;;;;;CAMA,OAAc,gBAAgB,UAAkB,kBAA2C;EACzF,OAAO,KAAK,OACV;GAAE,WAAW;GAAU,UAAU;GAAkB,YAAY;EAAK,GACpE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAC1B;CACF;;CAGA,OAAc,OAAO,UAAkB,kBAA2C;EAChF,OAAO,KAAK,OACV;GAAE,WAAW;GAAU,UAAU,mBAAmB;GAAG,YAAY;EAAK,GACxE,EAAE,MAAM,EAAE,4BAAY,IAAI,KAAK,EAAE,EAAE,CACrC;CACF;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AuthTokenFamily } from "./auth-token-family.model.mjs";
|
|
2
2
|
import { migrate } from "@warlock.js/cascade";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/models/auth-token-family/migration.ts
|
|
5
5
|
/** Creates the durable authority shared by every token in a refresh family. */
|
|
6
6
|
const AuthTokenFamilyMigration = migrate(AuthTokenFamily, {
|
|
7
7
|
name: "authTokenFamily",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"migration.mjs","names":[],"sources":["../../../../../../../../../packages/auth/src/models/auth-token-family/migration.ts"],"sourcesContent":["import { migrate } from \"@warlock.js/cascade\";\nimport { AuthTokenFamily } from \"./auth-token-family.model\";\n\n/** Creates the durable authority shared by every token in a refresh family. */\nexport const AuthTokenFamilyMigration = migrate(AuthTokenFamily, {\n name: \"authTokenFamily\",\n up() {\n this.createTableIfNotExists();\n this.primaryUuid();\n this.string(\"family_id\", 64).unique();\n this.foreignId(\"user_id\").index();\n this.string(\"user_type\", 50).nullable();\n this.integer(\"revision\").default(0);\n this.timestamp(\"revoked_at\").nullable();\n this.timestamps();\n },\n down() {\n this.dropTableIfExists();\n },\n});\n"],"mappings":";;;;;AAIA,MAAa,2BAA2B,QAAQ,iBAAiB;CAC/D,MAAM;CACN,KAAK;EACH,KAAK,uBAAuB;EAC5B,KAAK,YAAY;EACjB,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,OAAO;EACpC,KAAK,UAAU,SAAS,CAAC,CAAC,MAAM;EAChC,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,SAAS;EACtC,KAAK,QAAQ,UAAU,CAAC,CAAC,QAAQ,CAAC;EAClC,KAAK,UAAU,YAAY,CAAC,CAAC,SAAS;EACtC,KAAK,WAAW;CAClB;CACA,OAAO;EACL,KAAK,kBAAkB;CACzB;AACF,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { RefreshToken } from "./refresh-token/refresh-token.model.mjs";
|
|
|
3
3
|
import { Authenticable } from "../contracts/auth-contract.mjs";
|
|
4
4
|
import { ChildModel, Model, ModelSchema } from "@warlock.js/cascade";
|
|
5
5
|
|
|
6
|
-
//#region
|
|
6
|
+
//#region ../../packages/auth/src/models/auth.model.d.ts
|
|
7
7
|
declare abstract class Auth<Schema extends ModelSchema = ModelSchema> extends Model<Schema> implements Authenticable {
|
|
8
8
|
/**
|
|
9
9
|
* Get user type
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.model.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"auth.model.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/models/auth.model.ts"],"mappings":";;;;;;uBAMsB,IAAA,gBAAoB,WAAA,GAAc,WAAA,UAC9C,KAAA,CAAM,MAAA,aACH,aAAA;;AAFb;;eAOsB,QAAA;EAPoB;;;EAYjC,kBAAA,IAAkB,MAAA;EAOiB;;;EAA7B,eAAA,CAAgB,UAAA,GAAa,UAAA,GAAa,OAAA,CAAQ,SAAA;EAOjB;;;EAAjC,mBAAA,CAAoB,IAAA,SAAa,OAAA,CAAQ,iBAAA;EAcP;;;EAPlC,oBAAA,CAAqB,UAAA,GAAa,UAAA,GAAa,OAAA,CAAQ,YAAA;EAmC7B;;;EA5B1B,iBAAA,CAAkB,KAAA,WAAgB,OAAA;EAmCyB;;;EA5B3D,kBAAA,CAAmB,KAAA,WAAgB,OAAA;EA7CrC;;;EAoDE,qBAAA,IAAyB,OAAA;EAtDE;;;EA6D3B,eAAA,IAAmB,OAAA;EA3DrB;;;EAkEE,cAAA,IAAkB,OAAA,CAAQ,YAAA;EAjD1B;;;EAAA,OAwDO,OAAA,CAAQ,IAAA,EAAM,UAAA,CAAW,IAAA,GAAO,IAAA,QAAY,OAAA,CAAQ,IAAA;EAxDT;;;EA+DlD,eAAA,CAAgB,QAAA,WAAmB,OAAA;AAAA"}
|
|
@@ -2,7 +2,7 @@ import { authService } from "../services/auth.service.mjs";
|
|
|
2
2
|
import "../services/index.mjs";
|
|
3
3
|
import { Model } from "@warlock.js/cascade";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/auth/src/models/auth.model.ts
|
|
6
6
|
var Auth = class extends Model {
|
|
7
7
|
/**
|
|
8
8
|
* Get access token payload
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.model.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"auth.model.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/models/auth.model.ts"],"sourcesContent":["import { type ChildModel, Model, type ModelSchema } from \"@warlock.js/cascade\";\nimport type { Authenticable } from \"../contracts/auth-contract\";\nimport type { AccessTokenOutput, DeviceInfo, TokenPair } from \"../contracts/types\";\nimport { authService } from \"../services\";\nimport type { RefreshToken } from \"./refresh-token/refresh-token.model\";\n\nexport abstract class Auth<Schema extends ModelSchema = ModelSchema>\n extends Model<Schema>\n implements Authenticable\n{\n /**\n * Get user type\n */\n public abstract get userType(): string;\n\n /**\n * Get access token payload\n */\n public accessTokenPayload() {\n return authService.buildAccessTokenPayload(this);\n }\n\n /**\n * Create both access and refresh tokens\n */\n public async createTokenPair(deviceInfo?: DeviceInfo): Promise<TokenPair> {\n return authService.createTokenPair(this, deviceInfo);\n }\n\n /**\n * Generate access token\n */\n public async generateAccessToken(data?: any): Promise<AccessTokenOutput> {\n return authService.generateAccessToken(this, data);\n }\n\n /**\n * Generate refresh token\n */\n public async generateRefreshToken(deviceInfo?: DeviceInfo): Promise<RefreshToken | undefined> {\n return authService.createRefreshToken(this, deviceInfo);\n }\n\n /**\n * Remove current access token\n */\n public async removeAccessToken(token: string): Promise<void> {\n return authService.removeAccessToken(this, token);\n }\n\n /**\n * Remove refresh token\n */\n public async removeRefreshToken(token: string): Promise<void> {\n return authService.removeRefreshToken(this, token);\n }\n\n /**\n * Remove all access tokens\n */\n public async removeAllAccessTokens(): Promise<void> {\n return authService.removeAllAccessTokens(this);\n }\n\n /**\n * Revoke all tokens (logout from all devices)\n */\n public async revokeAllTokens(): Promise<void> {\n return authService.revokeAllTokens(this);\n }\n\n /**\n * Get active sessions\n */\n public async activeSessions(): Promise<RefreshToken[]> {\n return authService.getActiveSessions(this);\n }\n\n /**\n * Attempt to login the user\n */\n public static async attempt(this: ChildModel<Auth>, data: any): Promise<Auth | null> {\n return authService.attemptLogin(this, data);\n }\n\n /**\n * Confirm password\n */\n public async confirmPassword(password: string): Promise<boolean> {\n return authService.verifyPassword(password, this.string(\"password\")!);\n }\n}\n"],"mappings":";;;;;AAMA,IAAsB,OAAtB,cACU,MAEV;;;;CASE,AAAO,qBAAqB;EAC1B,OAAO,YAAY,wBAAwB,IAAI;CACjD;;;;CAKA,MAAa,gBAAgB,YAA6C;EACxE,OAAO,YAAY,gBAAgB,MAAM,UAAU;CACrD;;;;CAKA,MAAa,oBAAoB,MAAwC;EACvE,OAAO,YAAY,oBAAoB,MAAM,IAAI;CACnD;;;;CAKA,MAAa,qBAAqB,YAA4D;EAC5F,OAAO,YAAY,mBAAmB,MAAM,UAAU;CACxD;;;;CAKA,MAAa,kBAAkB,OAA8B;EAC3D,OAAO,YAAY,kBAAkB,MAAM,KAAK;CAClD;;;;CAKA,MAAa,mBAAmB,OAA8B;EAC5D,OAAO,YAAY,mBAAmB,MAAM,KAAK;CACnD;;;;CAKA,MAAa,wBAAuC;EAClD,OAAO,YAAY,sBAAsB,IAAI;CAC/C;;;;CAKA,MAAa,kBAAiC;EAC5C,OAAO,YAAY,gBAAgB,IAAI;CACzC;;;;CAKA,MAAa,iBAA0C;EACrD,OAAO,YAAY,kBAAkB,IAAI;CAC3C;;;;CAKA,aAAoB,QAAgC,MAAiC;EACnF,OAAO,YAAY,aAAa,MAAM,IAAI;CAC5C;;;;CAKA,MAAa,gBAAgB,UAAoC;EAC/D,OAAO,YAAY,eAAe,UAAU,KAAK,OAAO,UAAU,CAAE;CACtE;AACF"}
|