@warlock.js/auth 5.19.1 → 5.20.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 +6 -0
- package/esm/middleware/login-throttle.middleware.mjs +6 -2
- package/esm/middleware/login-throttle.middleware.mjs.map +1 -1
- package/llms-full.txt +1565 -1564
- package/llms.txt +8 -8
- package/package.json +9 -9
- package/skills/customize-token-storage/SKILL.md +160 -160
- package/skills/customize-user-type/SKILL.md +168 -168
- package/skills/handle-login-and-logout/SKILL.md +179 -179
- package/skills/index.md +22 -0
- package/skills/login-with-providers/SKILL.md +283 -283
- package/skills/manage-tokens/SKILL.md +15 -15
- package/skills/protect-routes/SKILL.md +288 -288
- package/skills/register-user/SKILL.md +147 -147
- package/skills/run-auth-commands/SKILL.md +159 -159
- package/skills/throttle-login-attempts/SKILL.md +2 -1
- package/skills/verify-email-and-reset-password/SKILL.md +137 -137
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.20.0 - 2026-09-24
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `loginThrottleMiddleware` now counts failures with an atomic `cache.increment`. The first failure opens the window with a create-only `cache.set` carrying the TTL, so counts are exact across servers on the redis/pg cache drivers (concurrent failures are no longer lost to a read-modify-write). Thresholds, window and lockout behaviour are unchanged. The memory cache driver is per-process, so counts there are per-process too.
|
|
12
|
+
|
|
7
13
|
## 5.19.0 - 2026-09-23
|
|
8
14
|
|
|
9
15
|
### Added
|
|
@@ -90,8 +90,12 @@ function loginThrottleMiddleware(options = {}) {
|
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
for (const identifier of identifiers) {
|
|
93
|
-
const
|
|
94
|
-
|
|
93
|
+
const key = counterKey(identifier);
|
|
94
|
+
await cache.set(key, 0, {
|
|
95
|
+
ttl: window,
|
|
96
|
+
onConflict: "create"
|
|
97
|
+
});
|
|
98
|
+
if (await cache.increment(key) >= max) await cache.set(lockKey(identifier), true, { ttl: lockoutDuration });
|
|
95
99
|
}
|
|
96
100
|
} catch (error) {
|
|
97
101
|
log.error("auth", "login-throttle", error);
|
|
@@ -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\";\r\nimport { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\r\n\r\n/**\r\n * Built-in identifier kinds the throttle knows how to extract from a request.\r\n *\r\n * - `\"email\"` tracks per-account, so an attacker can't spray one user from\r\n * many IPs.\r\n * - `\"ip\"` tracks per-source, so an attacker can't stuff many accounts from\r\n * one IP.\r\n *\r\n * Lockout trips when ANY tracked identifier crosses `max` — defense-in-depth.\r\n */\r\nexport type LoginThrottleIdentifier = \"email\" | \"ip\";\r\n\r\n/**\r\n * Options for {@link loginThrottleMiddleware}.\r\n *\r\n * Failure-aware by design: only counts requests whose response is non-2xx\r\n * (or whatever `isFailure` says) and resets on success, so a user who types\r\n * a wrong password three times then the right one is not penalised.\r\n *\r\n * @example\r\n * loginThrottleMiddleware({ max: 5, window: \"15m\", lockoutDuration: \"15m\" });\r\n */\r\nexport type LoginThrottleOptions = {\r\n /**\r\n * Failures allowed within the window before the identifier is locked.\r\n *\r\n * @default 5\r\n */\r\n max?: number;\r\n\r\n /**\r\n * Counting window. `ms`-format string (`\"15m\"`, `\"1h\"`) or seconds as a number.\r\n *\r\n * @default \"15m\"\r\n */\r\n window?: string | number;\r\n\r\n /**\r\n * Lockout duration once the threshold is tripped. `ms`-format string or seconds.\r\n *\r\n * @default \"15m\"\r\n */\r\n lockoutDuration?: string | number;\r\n\r\n /**\r\n * Identifiers tracked. Each is counted and locked independently.\r\n *\r\n * @default [\"email\", \"ip\"]\r\n */\r\n by?: LoginThrottleIdentifier[];\r\n\r\n /**\r\n * Credential field consulted when `\"email\"` is tracked. Switch to\r\n * `\"username\"` (or any other input field) for systems that don't\r\n * authenticate by email.\r\n *\r\n * @default \"email\"\r\n */\r\n identifierKey?: string;\r\n\r\n /**\r\n * Override the default error message returned with the 429 response.\r\n */\r\n errorMessage?: string;\r\n\r\n /**\r\n * Decide whether the response represents a failed login. Defaults to\r\n * \"non-2xx is a failure\"; override if your controller signals failure\r\n * through a 200-style body.\r\n *\r\n * @default (response) => !response.isOk\r\n */\r\n isFailure?: (response: Response) => boolean;\r\n\r\n /**\r\n * Compute the identifiers tracked for this request. Override to derive the\r\n * account key from a header, a JWT subject, or any other shape that does\r\n * not match the built-in `email` / `ip` extraction.\r\n */\r\n identify?: (request: Request) => string[];\r\n};\r\n\r\nconst COUNTER_PREFIX = \"auth.throttle.count.\";\r\nconst LOCK_PREFIX = \"auth.throttle.lock.\";\r\n\r\nconst counterKey = (identifier: string): string => `${COUNTER_PREFIX}${identifier}`;\r\nconst lockKey = (identifier: string): string => `${LOCK_PREFIX}${identifier}`;\r\n\r\n/**\r\n * Built-in identifier extractor — reads the configured credential field from\r\n * the request body for the per-account key, and `request.detectIp()` for the\r\n * per-source key. Untruthy values are dropped; a request that yields zero\r\n * identifiers is left untracked so the middleware no-ops for it.\r\n */\r\nfunction buildDefaultIdentifiers(\r\n request: Request,\r\n tracked: LoginThrottleIdentifier[],\r\n credentialField: string,\r\n): string[] {\r\n const identifiers: string[] = [];\r\n\r\n if (tracked.includes(\"email\")) {\r\n const value = request.input(credentialField);\r\n\r\n if (typeof value === \"string\" && value.length > 0) {\r\n identifiers.push(`email.${value.toLowerCase()}`);\r\n }\r\n }\r\n\r\n if (tracked.includes(\"ip\")) {\r\n const ip = request.detectIp();\r\n\r\n if (ip) {\r\n identifiers.push(`ip.${ip}`);\r\n }\r\n }\r\n\r\n return identifiers;\r\n}\r\n\r\n/**\r\n * Failure-aware login throttle. Locks an identifier (account, IP, or both)\r\n * after too many failed login attempts within a window — without penalising\r\n * a user who eventually types the right password.\r\n *\r\n * Mechanism (per identifier):\r\n *\r\n * 1. **Before the controller** — reject with `429` if a lock key is set,\r\n * skipping the DB lookup AND the bcrypt verify (this also neutralises the\r\n * CPU-DoS angle of brute-forcing).\r\n * 2. **After the controller** — observe the outcome via `response.onSent`.\r\n * On failure (non-2xx by default), bump a fixed-window counter; once it\r\n * reaches `max`, set a lock key with `lockoutDuration` TTL. On success,\r\n * clear both the counter and the lock for that identifier.\r\n *\r\n * Storage is `@warlock.js/cache` (a peer dependency of `@warlock.js/auth`,\r\n * transitively guaranteed via `@warlock.js/core`). Lockout state is shared\r\n * across replicas — correct security model, because an attacker can't bypass\r\n * by round-robin to a different pod. Every cache call is guarded so a\r\n * misconfigured driver fails open (a throttle outage must never escalate\r\n * into a full auth outage).\r\n *\r\n * @example\r\n * import { loginThrottleMiddleware } from \"@warlock.js/auth\";\r\n * import { router } from \"@warlock.js/core\";\r\n *\r\n * router.post(\"/auth/login\", loginController, {\r\n * middleware: [loginThrottleMiddleware({ max: 5, window: \"15m\" })],\r\n * });\r\n *\r\n * @example\r\n * // Per-IP only — avoid account-lockout-as-DoS on anonymous-attack vectors\r\n * router.post(\"/auth/refresh-token\", refreshController, {\r\n * middleware: [loginThrottleMiddleware({ by: [\"ip\"], max: 10, window: \"1m\" })],\r\n * });\r\n */\r\nexport function loginThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\r\n const max = options.max ?? 5;\r\n const window = options.window ?? \"15m\";\r\n const lockoutDuration = options.lockoutDuration ?? \"15m\";\r\n const tracked = options.by ?? [\"email\", \"ip\"];\r\n const credentialField = options.identifierKey ?? \"email\";\r\n\r\n return async ({ request, response }) => {\r\n const identifiers = options.identify\r\n ? options.identify(request)\r\n : buildDefaultIdentifiers(request, tracked, credentialField);\r\n\r\n if (identifiers.length === 0) return;\r\n\r\n try {\r\n for (const identifier of identifiers) {\r\n const locked = await cache.get(lockKey(identifier));\r\n\r\n if (locked) {\r\n return response.tooManyRequests({\r\n error: options.errorMessage ?? t(\"auth.errors.tooManyAttempts\"),\r\n errorCode: AuthErrorCodes.TooManyAttempts,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // Fail open — a cache outage (driver not initialised, network blip)\r\n // must never escalate into a full auth outage. Log and let the request\r\n // through; the onSent recorder below is independently guarded so an\r\n // outage there is also harmless.\r\n log.error(\"auth\", \"login-throttle\", error);\r\n\r\n return;\r\n }\r\n\r\n response.onSent(async (resolvedResponse: Response) => {\r\n try {\r\n const failed = options.isFailure\r\n ? options.isFailure(resolvedResponse)\r\n : !resolvedResponse.isOk;\r\n\r\n if (!failed) {\r\n for (const identifier of identifiers) {\r\n await cache.remove(counterKey(identifier));\r\n await cache.remove(lockKey(identifier));\r\n }\r\n\r\n return;\r\n }\r\n\r\n for (const identifier of identifiers) {\r\n const existing = await cache.get(counterKey(identifier));\r\n const ttlOption = existing === null ? { ttl: window } : undefined;\r\n const count = await cache.update<number>(\r\n counterKey(identifier),\r\n (current) => (current ?? 0) + 1,\r\n ttlOption,\r\n );\r\n\r\n if ((count ?? 0) >= max) {\r\n await cache.set(lockKey(identifier), true, { ttl: lockoutDuration });\r\n }\r\n }\r\n } catch (error) {\r\n log.error(\"auth\", \"login-throttle\", error);\r\n }\r\n });\r\n };\r\n}\r\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;KAEpC,MAAM,YAAY,MADK,MAAM,IAAI,WAAW,UAAU,CAAC,MACxB,OAAO,EAAE,KAAK,OAAO,IAAI;KAOxD,KAAK,MANe,MAAM,OACxB,WAAW,UAAU,IACpB,aAAa,WAAW,KAAK,GAC9B,SACF,KAEc,MAAM,KAClB,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":["../../../../../../../auth/src/middleware/login-throttle.middleware.ts"],"sourcesContent":["import { cache } from \"@warlock.js/cache\";\r\nimport { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\r\n\r\n/**\r\n * Built-in identifier kinds the throttle knows how to extract from a request.\r\n *\r\n * - `\"email\"` tracks per-account, so an attacker can't spray one user from\r\n * many IPs.\r\n * - `\"ip\"` tracks per-source, so an attacker can't stuff many accounts from\r\n * one IP.\r\n *\r\n * Lockout trips when ANY tracked identifier crosses `max` — defense-in-depth.\r\n */\r\nexport type LoginThrottleIdentifier = \"email\" | \"ip\";\r\n\r\n/**\r\n * Options for {@link loginThrottleMiddleware}.\r\n *\r\n * Failure-aware by design: only counts requests whose response is non-2xx\r\n * (or whatever `isFailure` says) and resets on success, so a user who types\r\n * a wrong password three times then the right one is not penalised.\r\n *\r\n * @example\r\n * loginThrottleMiddleware({ max: 5, window: \"15m\", lockoutDuration: \"15m\" });\r\n */\r\nexport type LoginThrottleOptions = {\r\n /**\r\n * Failures allowed within the window before the identifier is locked.\r\n *\r\n * @default 5\r\n */\r\n max?: number;\r\n\r\n /**\r\n * Counting window. `ms`-format string (`\"15m\"`, `\"1h\"`) or seconds as a number.\r\n *\r\n * @default \"15m\"\r\n */\r\n window?: string | number;\r\n\r\n /**\r\n * Lockout duration once the threshold is tripped. `ms`-format string or seconds.\r\n *\r\n * @default \"15m\"\r\n */\r\n lockoutDuration?: string | number;\r\n\r\n /**\r\n * Identifiers tracked. Each is counted and locked independently.\r\n *\r\n * @default [\"email\", \"ip\"]\r\n */\r\n by?: LoginThrottleIdentifier[];\r\n\r\n /**\r\n * Credential field consulted when `\"email\"` is tracked. Switch to\r\n * `\"username\"` (or any other input field) for systems that don't\r\n * authenticate by email.\r\n *\r\n * @default \"email\"\r\n */\r\n identifierKey?: string;\r\n\r\n /**\r\n * Override the default error message returned with the 429 response.\r\n */\r\n errorMessage?: string;\r\n\r\n /**\r\n * Decide whether the response represents a failed login. Defaults to\r\n * \"non-2xx is a failure\"; override if your controller signals failure\r\n * through a 200-style body.\r\n *\r\n * @default (response) => !response.isOk\r\n */\r\n isFailure?: (response: Response) => boolean;\r\n\r\n /**\r\n * Compute the identifiers tracked for this request. Override to derive the\r\n * account key from a header, a JWT subject, or any other shape that does\r\n * not match the built-in `email` / `ip` extraction.\r\n */\r\n identify?: (request: Request) => string[];\r\n};\r\n\r\nconst COUNTER_PREFIX = \"auth.throttle.count.\";\r\nconst LOCK_PREFIX = \"auth.throttle.lock.\";\r\n\r\nconst counterKey = (identifier: string): string => `${COUNTER_PREFIX}${identifier}`;\r\nconst lockKey = (identifier: string): string => `${LOCK_PREFIX}${identifier}`;\r\n\r\n/**\r\n * Built-in identifier extractor — reads the configured credential field from\r\n * the request body for the per-account key, and `request.detectIp()` for the\r\n * per-source key. Untruthy values are dropped; a request that yields zero\r\n * identifiers is left untracked so the middleware no-ops for it.\r\n */\r\nfunction buildDefaultIdentifiers(\r\n request: Request,\r\n tracked: LoginThrottleIdentifier[],\r\n credentialField: string,\r\n): string[] {\r\n const identifiers: string[] = [];\r\n\r\n if (tracked.includes(\"email\")) {\r\n const value = request.input(credentialField);\r\n\r\n if (typeof value === \"string\" && value.length > 0) {\r\n identifiers.push(`email.${value.toLowerCase()}`);\r\n }\r\n }\r\n\r\n if (tracked.includes(\"ip\")) {\r\n const ip = request.detectIp();\r\n\r\n if (ip) {\r\n identifiers.push(`ip.${ip}`);\r\n }\r\n }\r\n\r\n return identifiers;\r\n}\r\n\r\n/**\r\n * Failure-aware login throttle. Locks an identifier (account, IP, or both)\r\n * after too many failed login attempts within a window — without penalising\r\n * a user who eventually types the right password.\r\n *\r\n * Mechanism (per identifier):\r\n *\r\n * 1. **Before the controller** — reject with `429` if a lock key is set,\r\n * skipping the DB lookup AND the bcrypt verify (this also neutralises the\r\n * CPU-DoS angle of brute-forcing).\r\n * 2. **After the controller** — observe the outcome via `response.onSent`.\r\n * On failure (non-2xx by default), bump a fixed-window counter; once it\r\n * reaches `max`, set a lock key with `lockoutDuration` TTL. On success,\r\n * clear both the counter and the lock for that identifier.\r\n *\r\n * Storage is `@warlock.js/cache` (a peer dependency of `@warlock.js/auth`,\r\n * transitively guaranteed via `@warlock.js/core`). Lockout state is shared\r\n * across replicas — correct security model, because an attacker can't bypass\r\n * by round-robin to a different pod. Every cache call is guarded so a\r\n * misconfigured driver fails open (a throttle outage must never escalate\r\n * into a full auth outage).\r\n *\r\n * @example\r\n * import { loginThrottleMiddleware } from \"@warlock.js/auth\";\r\n * import { router } from \"@warlock.js/core\";\r\n *\r\n * router.post(\"/auth/login\", loginController, {\r\n * middleware: [loginThrottleMiddleware({ max: 5, window: \"15m\" })],\r\n * });\r\n *\r\n * @example\r\n * // Per-IP only — avoid account-lockout-as-DoS on anonymous-attack vectors\r\n * router.post(\"/auth/refresh-token\", refreshController, {\r\n * middleware: [loginThrottleMiddleware({ by: [\"ip\"], max: 10, window: \"1m\" })],\r\n * });\r\n */\r\nexport function loginThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\r\n const max = options.max ?? 5;\r\n const window = options.window ?? \"15m\";\r\n const lockoutDuration = options.lockoutDuration ?? \"15m\";\r\n const tracked = options.by ?? [\"email\", \"ip\"];\r\n const credentialField = options.identifierKey ?? \"email\";\r\n\r\n return async ({ request, response }) => {\r\n const identifiers = options.identify\r\n ? options.identify(request)\r\n : buildDefaultIdentifiers(request, tracked, credentialField);\r\n\r\n if (identifiers.length === 0) return;\r\n\r\n try {\r\n for (const identifier of identifiers) {\r\n const locked = await cache.get(lockKey(identifier));\r\n\r\n if (locked) {\r\n return response.tooManyRequests({\r\n error: options.errorMessage ?? t(\"auth.errors.tooManyAttempts\"),\r\n errorCode: AuthErrorCodes.TooManyAttempts,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // Fail open — a cache outage (driver not initialised, network blip)\r\n // must never escalate into a full auth outage. Log and let the request\r\n // through; the onSent recorder below is independently guarded so an\r\n // outage there is also harmless.\r\n log.error(\"auth\", \"login-throttle\", error);\r\n\r\n return;\r\n }\r\n\r\n response.onSent(async (resolvedResponse: Response) => {\r\n try {\r\n const failed = options.isFailure\r\n ? options.isFailure(resolvedResponse)\r\n : !resolvedResponse.isOk;\r\n\r\n if (!failed) {\r\n for (const identifier of identifiers) {\r\n await cache.remove(counterKey(identifier));\r\n await cache.remove(lockKey(identifier));\r\n }\r\n\r\n return;\r\n }\r\n\r\n for (const identifier of identifiers) {\r\n const key = counterKey(identifier);\r\n\r\n // Open the window on the first failure: the create-only write means\r\n // only one of several racing servers sets the TTL, and an existing\r\n // window keeps its deadline (fixed window). The bump itself is an\r\n // atomic increment, so concurrent failures across servers are never\r\n // lost (redis INCRBY / pg upsert keep the window's TTL).\r\n await cache.set(key, 0, { ttl: window, onConflict: \"create\" });\r\n const count = await cache.increment(key);\r\n\r\n if (count >= max) {\r\n await cache.set(lockKey(identifier), true, { ttl: lockoutDuration });\r\n }\r\n }\r\n } catch (error) {\r\n log.error(\"auth\", \"login-throttle\", error);\r\n }\r\n });\r\n };\r\n}\r\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"}
|