@shipfox/api-auth 2.0.0 → 4.0.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +28 -0
- package/README.md +7 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/login-methods.d.ts +3 -0
- package/dist/login-methods.d.ts.map +1 -0
- package/dist/login-methods.js +9 -0
- package/dist/login-methods.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +1 -0
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +24 -15
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/config.ts +5 -1
- package/src/index.test.ts +8 -0
- package/src/index.ts +3 -0
- package/src/login-methods.ts +5 -0
- package/src/presentation/auth/refresh-cookie.test.ts +1 -0
- package/src/presentation/routes/index.test.ts +67 -0
- package/src/presentation/routes/index.ts +22 -16
- package/test/routes.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vitest.config.ts +3 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
2
|
+
Successfully compiled: 54 files with swc (320.35ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @shipfox/api-auth
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0b0a9c2: Serializes real-database auth test files to prevent shared rate-limit state from causing intermittent failures.
|
|
8
|
+
- Updated dependencies [bbba3b7]
|
|
9
|
+
- @shipfox/node-drizzle@0.3.0
|
|
10
|
+
- @shipfox/api-workspaces@4.0.0
|
|
11
|
+
- @shipfox/node-module@0.3.1
|
|
12
|
+
- @shipfox/node-outbox@0.2.2
|
|
13
|
+
|
|
14
|
+
## 3.0.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- 3976f8c: Adds module login-method declarations, validates server compositions before startup, and adds password-login route configuration.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [3976f8c]
|
|
23
|
+
- Updated dependencies [7a71e7d]
|
|
24
|
+
- @shipfox/node-module@0.3.0
|
|
25
|
+
- @shipfox/node-opentelemetry@0.5.1
|
|
26
|
+
- @shipfox/api-workspaces@3.0.0
|
|
27
|
+
- @shipfox/node-fastify@0.2.2
|
|
28
|
+
- @shipfox/node-mailer@0.1.3
|
|
29
|
+
- @shipfox/api-auth-context@3.0.0
|
|
30
|
+
|
|
3
31
|
## 2.0.0
|
|
4
32
|
|
|
5
33
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ Required environment:
|
|
|
46
46
|
| `AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS` | `14` | Refresh token and cookie lifetime. |
|
|
47
47
|
| `AUTH_REFRESH_ROTATION_GRACE_SECONDS` | `30` | Grace window for accepting a just-rotated refresh token during concurrent refreshes. |
|
|
48
48
|
| `AUTH_REFRESH_COOKIE_NAME` | `shipfox_refresh_token` | HTTP cookie name for refresh sessions. |
|
|
49
|
+
| `AUTH_PASSWORD_ENABLED` | `true` | Enables password and email-verification routes. Set to `false` when another module provides login. Server construction fails if no login method is available. |
|
|
49
50
|
| `RATE_LIMIT_IDENTIFIER_SECRET` | none | Optional secret used to HMAC identifiers before storing rate-limit counters. When unset, the module derives a stable key from `AUTH_JWT_SECRET`. |
|
|
50
51
|
| `CLIENT_BASE_URL` | `http://localhost:5173` | Base URL used in email verification and password reset links. |
|
|
51
52
|
| `MAILER_TRANSPORT` | `console` | Mail transport. Set to `smtp` to send real mail. |
|
|
@@ -55,6 +56,8 @@ Required environment:
|
|
|
55
56
|
| `SMTP_USER` | none | Optional SMTP user. |
|
|
56
57
|
| `SMTP_PASSWORD` | none | Optional SMTP password. |
|
|
57
58
|
|
|
59
|
+
When `AUTH_PASSWORD_ENABLED=false`, the module does not register signup, login, password-reset, password-change, or email-verification routes. Refresh, logout, and current-session routes stay available. Another module must contribute a login method before the API server starts.
|
|
60
|
+
|
|
58
61
|
## Security model
|
|
59
62
|
|
|
60
63
|
The module issues three kinds of bearer token, all presented as
|
|
@@ -197,6 +200,8 @@ All routes are mounted under `/auth`.
|
|
|
197
200
|
|
|
198
201
|
Protected routes use the `Authorization: Bearer <token>` header. The refresh flow uses an HTTP-only cookie on the `/auth` path.
|
|
199
202
|
|
|
203
|
+
When password login is disabled, the password and email-verification rows in this table return `404`. Refresh, logout, and `me` remain registered.
|
|
204
|
+
|
|
200
205
|
> [!IMPORTANT]
|
|
201
206
|
> Refresh cookies are set with `secure: true`, `httpOnly: true`, and `sameSite: "lax"`. Local browser tests need HTTPS or a test path that handles secure cookies.
|
|
202
207
|
|
|
@@ -280,6 +285,8 @@ exists, it returns that user unchanged. It does not change the name, email,
|
|
|
280
285
|
status, verification state, or password. Repeated and concurrent callbacks are
|
|
281
286
|
safe.
|
|
282
287
|
|
|
288
|
+
This pre-verified user state lets an external login method create a session when password login and email-verification routes are disabled.
|
|
289
|
+
|
|
283
290
|
`createSessionForUser` accepts either a `userId` or an `email`. It only creates
|
|
284
291
|
a session for an active, verified user. It can throw `UserNotFoundError`,
|
|
285
292
|
`EmailNotVerifiedError`, `InvalidCredentialsError`, or
|
package/dist/config.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare const config: Readonly<{
|
|
|
9
9
|
AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: number;
|
|
10
10
|
AUTH_REFRESH_ROTATION_GRACE_SECONDS: number;
|
|
11
11
|
AUTH_REFRESH_COOKIE_NAME: string;
|
|
12
|
+
AUTH_PASSWORD_ENABLED: boolean;
|
|
12
13
|
RATE_LIMIT_IDENTIFIER_SECRET: string | undefined;
|
|
13
14
|
CLIENT_BASE_URL: string;
|
|
14
15
|
MAILER_TRANSPORT: "console" | "smtp";
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAwC,KAAK,MAAM,EAAC,MAAM,sBAAsB,CAAC;AAExF,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAwC,KAAK,MAAM,EAAC,MAAM,sBAAsB,CAAC;AAExF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;kDAuEjB,CAAC;AAiBH,eAAO,MAAM,MAAM,QAAiB,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createConfig, num, str } from '@shipfox/config';
|
|
1
|
+
import { bool, createConfig, num, str } from '@shipfox/config';
|
|
2
2
|
import { createConsoleMailer, createSmtpMailer } from '@shipfox/node-mailer';
|
|
3
3
|
export const config = createConfig({
|
|
4
4
|
AUTH_JWT_SECRET: str({
|
|
@@ -34,6 +34,10 @@ export const config = createConfig({
|
|
|
34
34
|
desc: 'Name of the browser cookie that stores the refresh token.',
|
|
35
35
|
default: 'shipfox_refresh_token'
|
|
36
36
|
}),
|
|
37
|
+
AUTH_PASSWORD_ENABLED: bool({
|
|
38
|
+
desc: 'Whether password login is available. Use true or false. Defaults to true. When false, password and email-verification routes are not registered, and server startup requires another module to contribute a login method.',
|
|
39
|
+
default: true
|
|
40
|
+
}),
|
|
37
41
|
RATE_LIMIT_IDENTIFIER_SECRET: str({
|
|
38
42
|
desc: 'Optional secret used to HMAC identifiers before storing rate-limit counters. Leave it unset to derive a stable key from AUTH_JWT_SECRET.',
|
|
39
43
|
default: undefined
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {createConfig, num, str} from '@shipfox/config';\nimport {createConsoleMailer, createSmtpMailer, type Mailer} from '@shipfox/node-mailer';\n\nexport const config = createConfig({\n AUTH_JWT_SECRET: str({\n desc: 'Secret used to sign and verify user access tokens (JWTs). Required, with no default, so startup fails when it is missing.',\n }),\n AUTH_JWT_EXPIRES_IN: str({\n desc: 'How long an access token stays valid. Accepts a duration string such as 15m, 1h, or 7d.',\n default: '15m',\n }),\n AUTH_JOB_LEASE_TOKEN_SECRET: str({\n desc: 'Secret used to sign and verify job lease tokens. Required, with no default, so startup fails when it is missing.',\n }),\n AUTH_JOB_LEASE_TOKEN_EXPIRES_IN: str({\n desc: 'How long a job lease token stays valid. Set it longer than the longest job (JOB_MAX_DURATION is 60 minutes) plus a safety margin.',\n default: '90m',\n }),\n AUTH_RUNNER_SESSION_TOKEN_SECRET: str({\n desc: 'Secret used to sign and verify runner session tokens. Required, with no default, so startup fails when it is missing.',\n }),\n AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN: str({\n desc: 'How long a runner session token stays valid. A revoked registration token can leave existing sessions usable until this lifetime ends.',\n default: '1h',\n }),\n AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: num({\n desc: 'How many days a refresh token stays valid before the user must sign in again.',\n default: 14,\n }),\n AUTH_REFRESH_ROTATION_GRACE_SECONDS: num({\n desc: 'Window after a refresh token is rotated during which the now-rotated token is still accepted, so concurrent refreshes from parallel tabs do not log the user out. Reuse past this window is treated as a compromise and revokes the session.',\n default: 30,\n }),\n AUTH_REFRESH_COOKIE_NAME: str({\n desc: 'Name of the browser cookie that stores the refresh token.',\n default: 'shipfox_refresh_token',\n }),\n RATE_LIMIT_IDENTIFIER_SECRET: str({\n desc: 'Optional secret used to HMAC identifiers before storing rate-limit counters. Leave it unset to derive a stable key from AUTH_JWT_SECRET.',\n default: undefined,\n }),\n CLIENT_BASE_URL: str({\n desc: 'Base URL of the client app. Used to build links in emails such as password resets.',\n default: 'http://localhost:5173',\n }),\n MAILER_TRANSPORT: str({\n desc: 'How emails are delivered. Use console to print them to the log, or smtp to send them through an SMTP server.',\n choices: ['console', 'smtp'],\n default: 'console',\n }),\n MAILER_FROM: str({\n desc: 'Sender address shown on outgoing emails.',\n default: 'noreply@shipfox.local',\n }),\n SMTP_HOST: str({\n desc: 'Hostname of the SMTP server. Required when MAILER_TRANSPORT is smtp.',\n default: undefined,\n }),\n SMTP_PORT: num({\n desc: 'Port of the SMTP server.',\n default: 587,\n }),\n SMTP_USER: str({\n desc: 'Username for SMTP authentication. Leave it unset if the server needs no login.',\n default: undefined,\n }),\n SMTP_PASSWORD: str({\n desc: 'Password for SMTP authentication. Leave it unset if the server needs no login.',\n default: undefined,\n }),\n});\n\nfunction createMailer(): Mailer {\n if (config.MAILER_TRANSPORT === 'smtp') {\n if (!config.SMTP_HOST) throw new Error('SMTP_HOST is required when MAILER_TRANSPORT=smtp');\n return createSmtpMailer({\n host: config.SMTP_HOST,\n port: config.SMTP_PORT,\n ...(config.SMTP_USER ? {user: config.SMTP_USER} : {}),\n ...(config.SMTP_PASSWORD ? {password: config.SMTP_PASSWORD} : {}),\n from: config.MAILER_FROM,\n });\n }\n\n return createConsoleMailer({from: config.MAILER_FROM});\n}\n\nexport const mailer = createMailer();\n"],"names":["createConfig","num","str","createConsoleMailer","createSmtpMailer","config","AUTH_JWT_SECRET","desc","AUTH_JWT_EXPIRES_IN","default","AUTH_JOB_LEASE_TOKEN_SECRET","AUTH_JOB_LEASE_TOKEN_EXPIRES_IN","AUTH_RUNNER_SESSION_TOKEN_SECRET","AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN","AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS","AUTH_REFRESH_ROTATION_GRACE_SECONDS","AUTH_REFRESH_COOKIE_NAME","RATE_LIMIT_IDENTIFIER_SECRET","undefined","CLIENT_BASE_URL","MAILER_TRANSPORT","choices","MAILER_FROM","SMTP_HOST","SMTP_PORT","SMTP_USER","SMTP_PASSWORD","createMailer","Error","host","port","user","password","from","mailer"],"mappings":"AAAA,SAAQA,YAAY,EAAEC,GAAG,EAAEC,GAAG,QAAO,kBAAkB;
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {bool, createConfig, num, str} from '@shipfox/config';\nimport {createConsoleMailer, createSmtpMailer, type Mailer} from '@shipfox/node-mailer';\n\nexport const config = createConfig({\n AUTH_JWT_SECRET: str({\n desc: 'Secret used to sign and verify user access tokens (JWTs). Required, with no default, so startup fails when it is missing.',\n }),\n AUTH_JWT_EXPIRES_IN: str({\n desc: 'How long an access token stays valid. Accepts a duration string such as 15m, 1h, or 7d.',\n default: '15m',\n }),\n AUTH_JOB_LEASE_TOKEN_SECRET: str({\n desc: 'Secret used to sign and verify job lease tokens. Required, with no default, so startup fails when it is missing.',\n }),\n AUTH_JOB_LEASE_TOKEN_EXPIRES_IN: str({\n desc: 'How long a job lease token stays valid. Set it longer than the longest job (JOB_MAX_DURATION is 60 minutes) plus a safety margin.',\n default: '90m',\n }),\n AUTH_RUNNER_SESSION_TOKEN_SECRET: str({\n desc: 'Secret used to sign and verify runner session tokens. Required, with no default, so startup fails when it is missing.',\n }),\n AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN: str({\n desc: 'How long a runner session token stays valid. A revoked registration token can leave existing sessions usable until this lifetime ends.',\n default: '1h',\n }),\n AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: num({\n desc: 'How many days a refresh token stays valid before the user must sign in again.',\n default: 14,\n }),\n AUTH_REFRESH_ROTATION_GRACE_SECONDS: num({\n desc: 'Window after a refresh token is rotated during which the now-rotated token is still accepted, so concurrent refreshes from parallel tabs do not log the user out. Reuse past this window is treated as a compromise and revokes the session.',\n default: 30,\n }),\n AUTH_REFRESH_COOKIE_NAME: str({\n desc: 'Name of the browser cookie that stores the refresh token.',\n default: 'shipfox_refresh_token',\n }),\n AUTH_PASSWORD_ENABLED: bool({\n desc: 'Whether password login is available. Use true or false. Defaults to true. When false, password and email-verification routes are not registered, and server startup requires another module to contribute a login method.',\n default: true,\n }),\n RATE_LIMIT_IDENTIFIER_SECRET: str({\n desc: 'Optional secret used to HMAC identifiers before storing rate-limit counters. Leave it unset to derive a stable key from AUTH_JWT_SECRET.',\n default: undefined,\n }),\n CLIENT_BASE_URL: str({\n desc: 'Base URL of the client app. Used to build links in emails such as password resets.',\n default: 'http://localhost:5173',\n }),\n MAILER_TRANSPORT: str({\n desc: 'How emails are delivered. Use console to print them to the log, or smtp to send them through an SMTP server.',\n choices: ['console', 'smtp'],\n default: 'console',\n }),\n MAILER_FROM: str({\n desc: 'Sender address shown on outgoing emails.',\n default: 'noreply@shipfox.local',\n }),\n SMTP_HOST: str({\n desc: 'Hostname of the SMTP server. Required when MAILER_TRANSPORT is smtp.',\n default: undefined,\n }),\n SMTP_PORT: num({\n desc: 'Port of the SMTP server.',\n default: 587,\n }),\n SMTP_USER: str({\n desc: 'Username for SMTP authentication. Leave it unset if the server needs no login.',\n default: undefined,\n }),\n SMTP_PASSWORD: str({\n desc: 'Password for SMTP authentication. Leave it unset if the server needs no login.',\n default: undefined,\n }),\n});\n\nfunction createMailer(): Mailer {\n if (config.MAILER_TRANSPORT === 'smtp') {\n if (!config.SMTP_HOST) throw new Error('SMTP_HOST is required when MAILER_TRANSPORT=smtp');\n return createSmtpMailer({\n host: config.SMTP_HOST,\n port: config.SMTP_PORT,\n ...(config.SMTP_USER ? {user: config.SMTP_USER} : {}),\n ...(config.SMTP_PASSWORD ? {password: config.SMTP_PASSWORD} : {}),\n from: config.MAILER_FROM,\n });\n }\n\n return createConsoleMailer({from: config.MAILER_FROM});\n}\n\nexport const mailer = createMailer();\n"],"names":["bool","createConfig","num","str","createConsoleMailer","createSmtpMailer","config","AUTH_JWT_SECRET","desc","AUTH_JWT_EXPIRES_IN","default","AUTH_JOB_LEASE_TOKEN_SECRET","AUTH_JOB_LEASE_TOKEN_EXPIRES_IN","AUTH_RUNNER_SESSION_TOKEN_SECRET","AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN","AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS","AUTH_REFRESH_ROTATION_GRACE_SECONDS","AUTH_REFRESH_COOKIE_NAME","AUTH_PASSWORD_ENABLED","RATE_LIMIT_IDENTIFIER_SECRET","undefined","CLIENT_BASE_URL","MAILER_TRANSPORT","choices","MAILER_FROM","SMTP_HOST","SMTP_PORT","SMTP_USER","SMTP_PASSWORD","createMailer","Error","host","port","user","password","from","mailer"],"mappings":"AAAA,SAAQA,IAAI,EAAEC,YAAY,EAAEC,GAAG,EAAEC,GAAG,QAAO,kBAAkB;AAC7D,SAAQC,mBAAmB,EAAEC,gBAAgB,QAAoB,uBAAuB;AAExF,OAAO,MAAMC,SAASL,aAAa;IACjCM,iBAAiBJ,IAAI;QACnBK,MAAM;IACR;IACAC,qBAAqBN,IAAI;QACvBK,MAAM;QACNE,SAAS;IACX;IACAC,6BAA6BR,IAAI;QAC/BK,MAAM;IACR;IACAI,iCAAiCT,IAAI;QACnCK,MAAM;QACNE,SAAS;IACX;IACAG,kCAAkCV,IAAI;QACpCK,MAAM;IACR;IACAM,sCAAsCX,IAAI;QACxCK,MAAM;QACNE,SAAS;IACX;IACAK,oCAAoCb,IAAI;QACtCM,MAAM;QACNE,SAAS;IACX;IACAM,qCAAqCd,IAAI;QACvCM,MAAM;QACNE,SAAS;IACX;IACAO,0BAA0Bd,IAAI;QAC5BK,MAAM;QACNE,SAAS;IACX;IACAQ,uBAAuBlB,KAAK;QAC1BQ,MAAM;QACNE,SAAS;IACX;IACAS,8BAA8BhB,IAAI;QAChCK,MAAM;QACNE,SAASU;IACX;IACAC,iBAAiBlB,IAAI;QACnBK,MAAM;QACNE,SAAS;IACX;IACAY,kBAAkBnB,IAAI;QACpBK,MAAM;QACNe,SAAS;YAAC;YAAW;SAAO;QAC5Bb,SAAS;IACX;IACAc,aAAarB,IAAI;QACfK,MAAM;QACNE,SAAS;IACX;IACAe,WAAWtB,IAAI;QACbK,MAAM;QACNE,SAASU;IACX;IACAM,WAAWxB,IAAI;QACbM,MAAM;QACNE,SAAS;IACX;IACAiB,WAAWxB,IAAI;QACbK,MAAM;QACNE,SAASU;IACX;IACAQ,eAAezB,IAAI;QACjBK,MAAM;QACNE,SAASU;IACX;AACF,GAAG;AAEH,SAASS;IACP,IAAIvB,OAAOgB,gBAAgB,KAAK,QAAQ;QACtC,IAAI,CAAChB,OAAOmB,SAAS,EAAE,MAAM,IAAIK,MAAM;QACvC,OAAOzB,iBAAiB;YACtB0B,MAAMzB,OAAOmB,SAAS;YACtBO,MAAM1B,OAAOoB,SAAS;YACtB,GAAIpB,OAAOqB,SAAS,GAAG;gBAACM,MAAM3B,OAAOqB,SAAS;YAAA,IAAI,CAAC,CAAC;YACpD,GAAIrB,OAAOsB,aAAa,GAAG;gBAACM,UAAU5B,OAAOsB,aAAa;YAAA,IAAI,CAAC,CAAC;YAChEO,MAAM7B,OAAOkB,WAAW;QAC1B;IACF;IAEA,OAAOpB,oBAAoB;QAAC+B,MAAM7B,OAAOkB,WAAW;IAAA;AACtD;AAEA,OAAO,MAAMY,SAASP,eAAe"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAiBxD,YAAY,EAAC,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AACzF,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,oBAAoB,EAAE,aAAa,EAAC,MAAM,eAAe,CAAC;AAClE,YAAY,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,8BAA8B,EAC9B,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAC,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAC,0BAA0B,EAAC,MAAM,wCAAwC,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAC,6BAA6B,EAAC,MAAM,2CAA2C,CAAC;AAIxF,eAAO,MAAM,UAAU,EAAE,aAYxB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AUTH_EMAIL_VERIFICATION_SEND_REQUESTED, AUTH_PASSWORD_RESET_SEND_REQUESTED, authEventSchemas } from '@shipfox/api-auth-dto';
|
|
2
2
|
import { subscriberFactory } from '@shipfox/node-module';
|
|
3
|
+
import { config } from '#config.js';
|
|
3
4
|
import { db } from '#db/db.js';
|
|
4
5
|
import { migrationsPath } from '#db/migrations.js';
|
|
5
6
|
import { authOutbox } from '#db/schema/outbox.js';
|
|
@@ -9,6 +10,7 @@ import { createRunnerSessionAuthMethod } from '#presentation/auth/runner-session
|
|
|
9
10
|
import { authE2eRoutes } from '#presentation/e2eRoutes/index.js';
|
|
10
11
|
import { authRoutes } from '#presentation/routes/index.js';
|
|
11
12
|
import { onEmailVerificationSendRequested, onPasswordResetSendRequested } from '#presentation/subscribers/index.js';
|
|
13
|
+
import { passwordLoginMethods } from './login-methods.js';
|
|
12
14
|
export { createSessionForUser, provisionUser } from '#core/auth.js';
|
|
13
15
|
export { AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidCredentialsError, UserNotFoundError } from '#core/errors.js';
|
|
14
16
|
export { issueJobLeaseToken, jobLeaseParamsFrom, verifyJobLeaseToken } from '#core/job-lease-token.js';
|
|
@@ -29,6 +31,7 @@ export const authModule = {
|
|
|
29
31
|
createLeaseTokenAuthMethod(),
|
|
30
32
|
createRunnerSessionAuthMethod()
|
|
31
33
|
],
|
|
34
|
+
loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),
|
|
32
35
|
routes: [
|
|
33
36
|
authRoutes
|
|
34
37
|
],
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_EMAIL_VERIFICATION_SEND_REQUESTED,\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {authE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {authRoutes} from '#presentation/routes/index.js';\nimport {\n onEmailVerificationSendRequested,\n onPasswordResetSendRequested,\n} from '#presentation/subscribers/index.js';\n\nexport type {JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidCredentialsError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport const authModule: ShipfoxModule = {\n name: 'auth',\n database: {db, migrationsPath},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n routes: [authRoutes],\n e2eRoutes: [authE2eRoutes],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authEventSchemas}],\n subscribers: [\n subscriber(AUTH_EMAIL_VERIFICATION_SEND_REQUESTED, onEmailVerificationSendRequested),\n subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested),\n ],\n};\n"],"names":["AUTH_EMAIL_VERIFICATION_SEND_REQUESTED","AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","subscriberFactory","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","authE2eRoutes","authRoutes","onEmailVerificationSendRequested","onPasswordResetSendRequested","createSessionForUser","provisionUser","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidCredentialsError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","authModule","name","database","auth","routes","e2eRoutes","publishers","table","eventSchemas","subscribers"],"mappings":"AAAA,SACEA,sCAAsC,EACtCC,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAE/B,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,aAAa,QAAO,mCAAmC;AAC/D,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SACEC,gCAAgC,EAChCC,4BAA4B,QACvB,qCAAqC;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_EMAIL_VERIFICATION_SEND_REQUESTED,\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {config} from '#config.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {authE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {authRoutes} from '#presentation/routes/index.js';\nimport {\n onEmailVerificationSendRequested,\n onPasswordResetSendRequested,\n} from '#presentation/subscribers/index.js';\nimport {passwordLoginMethods} from './login-methods.js';\n\nexport type {JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidCredentialsError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport const authModule: ShipfoxModule = {\n name: 'auth',\n database: {db, migrationsPath},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),\n routes: [authRoutes],\n e2eRoutes: [authE2eRoutes],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authEventSchemas}],\n subscribers: [\n subscriber(AUTH_EMAIL_VERIFICATION_SEND_REQUESTED, onEmailVerificationSendRequested),\n subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested),\n ],\n};\n"],"names":["AUTH_EMAIL_VERIFICATION_SEND_REQUESTED","AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","subscriberFactory","config","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","authE2eRoutes","authRoutes","onEmailVerificationSendRequested","onPasswordResetSendRequested","passwordLoginMethods","createSessionForUser","provisionUser","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidCredentialsError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","authModule","name","database","auth","loginMethods","AUTH_PASSWORD_ENABLED","routes","e2eRoutes","publishers","table","eventSchemas","subscribers"],"mappings":"AAAA,SACEA,sCAAsC,EACtCC,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAE/B,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,aAAa,QAAO,mCAAmC;AAC/D,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SACEC,gCAAgC,EAChCC,4BAA4B,QACvB,qCAAqC;AAC5C,SAAQC,oBAAoB,QAAO,qBAAqB;AASxD,SAAQC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElE,SACEC,8BAA8B,EAC9BC,qBAAqB,EACrBC,uBAAuB,EACvBC,iBAAiB,QACZ,kBAAkB;AACzB,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,QACd,2BAA2B;AAClC,SACEC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SAAQlB,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SACEkB,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQpB,6BAA6B,QAAO,4CAA4C;AAExF,MAAMqB,aAAa5B;AAEnB,OAAO,MAAM6B,aAA4B;IACvCC,MAAM;IACNC,UAAU;QAAC7B;QAAIC;IAAc;IAC7B6B,MAAM;QAAC3B;QAAuBC;QAA8BC;KAAgC;IAC5F0B,cAAcrB,qBAAqBX,OAAOiC,qBAAqB;IAC/DC,QAAQ;QAAC1B;KAAW;IACpB2B,WAAW;QAAC5B;KAAc;IAC1B6B,YAAY;QAAC;YAACP,MAAM;YAAQQ,OAAOlC;YAAYF;YAAIqC,cAAcxC;QAAgB;KAAE;IACnFyC,aAAa;QACXZ,WAAW/B,wCAAwCa;QACnDkB,WAAW9B,oCAAoCa;KAChD;AACH,EAAE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-methods.d.ts","sourceRoot":"","sources":["../src/login-methods.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAC;AAEtD,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,OAAO,GAAG,WAAW,EAAE,CAE5E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/login-methods.ts"],"sourcesContent":["import type {LoginMethod} from '@shipfox/node-module';\n\nexport function passwordLoginMethods(passwordEnabled: boolean): LoginMethod[] {\n return passwordEnabled ? [{id: 'password'}] : [];\n}\n"],"names":["passwordLoginMethods","passwordEnabled","id"],"mappings":"AAEA,OAAO,SAASA,qBAAqBC,eAAwB;IAC3D,OAAOA,kBAAkB;QAAC;YAACC,IAAI;QAAU;KAAE,GAAG,EAAE;AAClD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;AActD,wBAAgB,eAAe,CAAC,eAAe,EAAE,OAAO,GAAG,UAAU,CAkBpE;AAED,eAAO,MAAM,UAAU,YAAgD,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { config } from '#config.js';
|
|
1
2
|
import { authCookiePlugin } from '#presentation/auth/refresh-cookie.js';
|
|
2
3
|
import { verifyEmailConfirmRoute } from './email-verification/verify-email-confirm.js';
|
|
3
4
|
import { verifyEmailResendRoute } from './email-verification/verify-email-resend.js';
|
|
@@ -9,23 +10,31 @@ import { loginRoute } from './session/login.js';
|
|
|
9
10
|
import { logoutRoute } from './session/logout.js';
|
|
10
11
|
import { meRoute } from './session/me.js';
|
|
11
12
|
import { refreshRoute } from './session/refresh.js';
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
plugins: [
|
|
15
|
-
authCookiePlugin
|
|
16
|
-
],
|
|
17
|
-
routes: [
|
|
13
|
+
export function buildAuthRoutes(passwordEnabled) {
|
|
14
|
+
const passwordRoutes = passwordEnabled ? [
|
|
18
15
|
signupRoute,
|
|
19
16
|
verifyEmailConfirmRoute,
|
|
20
17
|
verifyEmailResendRoute,
|
|
21
|
-
loginRoute
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
loginRoute
|
|
19
|
+
] : [];
|
|
20
|
+
return {
|
|
21
|
+
prefix: '/auth',
|
|
22
|
+
plugins: [
|
|
23
|
+
authCookiePlugin
|
|
24
|
+
],
|
|
25
|
+
routes: [
|
|
26
|
+
...passwordRoutes,
|
|
27
|
+
refreshRoute,
|
|
28
|
+
logoutRoute,
|
|
29
|
+
meRoute,
|
|
30
|
+
...passwordEnabled ? [
|
|
31
|
+
changePasswordRoute,
|
|
32
|
+
passwordResetRequestRoute,
|
|
33
|
+
passwordResetConfirmRoute
|
|
34
|
+
] : []
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export const authRoutes = buildAuthRoutes(config.AUTH_PASSWORD_ENABLED);
|
|
30
39
|
|
|
31
40
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/index.ts"],"sourcesContent":["import type {RouteGroup} from '@shipfox/node-fastify';\nimport {authCookiePlugin} from '#presentation/auth/refresh-cookie.js';\nimport {verifyEmailConfirmRoute} from './email-verification/verify-email-confirm.js';\nimport {verifyEmailResendRoute} from './email-verification/verify-email-resend.js';\nimport {changePasswordRoute} from './password/change-password.js';\nimport {passwordResetConfirmRoute} from './password/password-reset-confirm.js';\nimport {passwordResetRequestRoute} from './password/password-reset-request.js';\nimport {signupRoute} from './registration/signup.js';\nimport {loginRoute} from './session/login.js';\nimport {logoutRoute} from './session/logout.js';\nimport {meRoute} from './session/me.js';\nimport {refreshRoute} from './session/refresh.js';\n\nexport
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/index.ts"],"sourcesContent":["import type {RouteGroup} from '@shipfox/node-fastify';\nimport {config} from '#config.js';\nimport {authCookiePlugin} from '#presentation/auth/refresh-cookie.js';\nimport {verifyEmailConfirmRoute} from './email-verification/verify-email-confirm.js';\nimport {verifyEmailResendRoute} from './email-verification/verify-email-resend.js';\nimport {changePasswordRoute} from './password/change-password.js';\nimport {passwordResetConfirmRoute} from './password/password-reset-confirm.js';\nimport {passwordResetRequestRoute} from './password/password-reset-request.js';\nimport {signupRoute} from './registration/signup.js';\nimport {loginRoute} from './session/login.js';\nimport {logoutRoute} from './session/logout.js';\nimport {meRoute} from './session/me.js';\nimport {refreshRoute} from './session/refresh.js';\n\nexport function buildAuthRoutes(passwordEnabled: boolean): RouteGroup {\n const passwordRoutes = passwordEnabled\n ? [signupRoute, verifyEmailConfirmRoute, verifyEmailResendRoute, loginRoute]\n : [];\n\n return {\n prefix: '/auth',\n plugins: [authCookiePlugin],\n routes: [\n ...passwordRoutes,\n refreshRoute,\n logoutRoute,\n meRoute,\n ...(passwordEnabled\n ? [changePasswordRoute, passwordResetRequestRoute, passwordResetConfirmRoute]\n : []),\n ],\n };\n}\n\nexport const authRoutes = buildAuthRoutes(config.AUTH_PASSWORD_ENABLED);\n"],"names":["config","authCookiePlugin","verifyEmailConfirmRoute","verifyEmailResendRoute","changePasswordRoute","passwordResetConfirmRoute","passwordResetRequestRoute","signupRoute","loginRoute","logoutRoute","meRoute","refreshRoute","buildAuthRoutes","passwordEnabled","passwordRoutes","prefix","plugins","routes","authRoutes","AUTH_PASSWORD_ENABLED"],"mappings":"AACA,SAAQA,MAAM,QAAO,aAAa;AAClC,SAAQC,gBAAgB,QAAO,uCAAuC;AACtE,SAAQC,uBAAuB,QAAO,+CAA+C;AACrF,SAAQC,sBAAsB,QAAO,8CAA8C;AACnF,SAAQC,mBAAmB,QAAO,gCAAgC;AAClE,SAAQC,yBAAyB,QAAO,uCAAuC;AAC/E,SAAQC,yBAAyB,QAAO,uCAAuC;AAC/E,SAAQC,WAAW,QAAO,2BAA2B;AACrD,SAAQC,UAAU,QAAO,qBAAqB;AAC9C,SAAQC,WAAW,QAAO,sBAAsB;AAChD,SAAQC,OAAO,QAAO,kBAAkB;AACxC,SAAQC,YAAY,QAAO,uBAAuB;AAElD,OAAO,SAASC,gBAAgBC,eAAwB;IACtD,MAAMC,iBAAiBD,kBACnB;QAACN;QAAaL;QAAyBC;QAAwBK;KAAW,GAC1E,EAAE;IAEN,OAAO;QACLO,QAAQ;QACRC,SAAS;YAACf;SAAiB;QAC3BgB,QAAQ;eACHH;YACHH;YACAF;YACAC;eACIG,kBACA;gBAACT;gBAAqBE;gBAA2BD;aAA0B,GAC3E,EAAE;SACP;IACH;AACF;AAEA,OAAO,MAAMa,aAAaN,gBAAgBZ,OAAOmB,qBAAqB,EAAE"}
|