@alxia/janus 0.3.18 → 0.4.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/README.md CHANGED
@@ -1,20 +1,28 @@
1
1
  # @alxia/janus
2
2
 
3
3
  Identities, sessions and permissions for [alxia](https://www.npmjs.com/package/@alxia/core),
4
- on [`@nxgt/janus`](https://www.npmjs.com/package/@nxgt/janus): your process,
4
+ on [`@softistx/janus`](https://github.com/orgs/softistx/packages/npm/package/janus): your process,
5
5
  your database. The user typed in the context, the session cookie kept and
6
6
  renewed, janus's refusals answered with their status and a safe body. No
7
7
  dependency.
8
8
 
9
9
  ```sh
10
- bun add @alxia/janus @nxgt/janus @alxia/core
10
+ bun add @alxia/janus @softistx/janus @alxia/core
11
11
  bun add -d typescript
12
12
  ```
13
13
 
14
- It mirrors [`@nxgt/janus-hono`](https://www.npmjs.com/package/@nxgt/janus-hono):
14
+ `@softistx/janus` is private, on GitHub Packages. Map its scope in your
15
+ `bunfig.toml` and export a token that reads packages (`read:packages`):
16
+
17
+ ```toml
18
+ [install.scopes]
19
+ "@softistx" = { url = "https://npm.pkg.github.com/", token = "$GITHUB_TOKEN" }
20
+ ```
21
+
22
+ It mirrors [`@softistx/janus-hono`](https://github.com/orgs/softistx/packages/npm/package/janus-hono):
15
23
  the same cookie, the same rules, the same bodies.
16
24
 
17
- A BFF that calls a janus API, rather than running `@nxgt/janus` itself, wants
25
+ A BFF that calls a janus API, rather than running `@softistx/janus` itself, wants
18
26
  [`@alxia/react-router/session`](https://www.npmjs.com/package/@alxia/react-router).
19
27
 
20
28
  ## Usage
@@ -22,7 +30,7 @@ A BFF that calls a janus API, rather than running `@nxgt/janus` itself, wants
22
30
  ```ts
23
31
  import { alxia, validate } from '@alxia/core';
24
32
  import { janusErrors, session } from '@alxia/janus';
25
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
33
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
26
34
  import { z } from 'zod';
27
35
 
28
36
  const accounts = janus({
@@ -107,14 +115,14 @@ before `createI18n()`, still answers it.
107
115
  | 403 | `USER_INACTIVE`, `STEP_UP_REQUIRED` |
108
116
  | 404 | `NOT_FOUND` |
109
117
  | 409 | `LOGIN_TAKEN`, `VERSION_CONFLICT`, `SECOND_FACTOR_*` |
110
- | 429 | `MAIL_THROTTLED` (`@nxgt/janus` 0.18+), with `retryAfter` and `Retry-After` |
118
+ | 429 | `MAIL_THROTTLED` (`@softistx/janus` 0.18+), with `retryAfter` and `Retry-After` |
111
119
  | 500 | `PERMISSION_DEPTH` |
112
120
  | 501 | `UNSUPPORTED` |
113
121
  | 503 | `STORE_FAILED` |
114
122
 
115
123
  ## `permission(access, permission, type, load, options?)`
116
124
 
117
- A guard on `@nxgt/janus/permissions`: the routes after it run only if the
125
+ A guard on `@softistx/janus/permissions`: the routes after it run only if the
118
126
  subject — the session's `user`, or `options.subject(ctx)` — holds
119
127
  `permission` on the object `load` finds, which they read as `object`.
120
128
  Anonymous is a 401, nothing loaded a 404, a denial a 403, each typed. Scope
@@ -123,8 +131,8 @@ it with `group`:
123
131
  ```ts
124
132
  import { alxia } from '@alxia/core';
125
133
  import { byParam, janusErrors, permission, session } from '@alxia/janus';
126
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
127
- import { createMemoryRelations, defineModel, permissions } from '@nxgt/janus/permissions';
134
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
135
+ import { createMemoryRelations, defineModel, permissions } from '@softistx/janus/permissions';
128
136
  import { z } from 'zod';
129
137
 
130
138
  const relations = createMemoryRelations();
@@ -190,9 +198,9 @@ on every app: annotate what it reads, or leave it unannotated.
190
198
  | --- | --- |
191
199
  | `session(accounts, options?)` | the middleware: `user`, `session`, `auth` |
192
200
  | `SessionOptions` | its options: `type`, `required`, `device` |
193
- | `RequestAuth`, `SessionOpened` | the type of `ctx.auth`: `send`, `signOut`, `device`; and what `send` takes: `token`, `session` and `user` from `@nxgt/janus`'s `SignedIn`, and `deviceToken?` |
201
+ | `RequestAuth`, `SessionOpened` | the type of `ctx.auth`: `send`, `signOut`, `device`; and what `send` takes: `token`, `session` and `user` from `@softistx/janus`'s `SignedIn`, and `deviceToken?` |
194
202
  | `sendSession`, `signOut`, `deviceOf`, `sendDevice` | the cookies, outside a route |
195
- | `DEVICE_COOKIE` | the device cookie's name, `janus-device`: the one `@nxgt/janus-hono` uses, so a device one remembers the other does too |
203
+ | `DEVICE_COOKIE` | the device cookie's name, `janus-device`: the one `@softistx/janus-hono` uses, so a device one remembers the other does too |
196
204
  | `SendSessionOptions`, `DeviceCookieOptions` | their options: `device`; `name`, `domain`, `path`, `sameSite`, `secure`, `maxAge` |
197
205
  | `janusErrors(options?)` | the middleware: janus's refusals answered; `use` it before `session()` |
198
206
  | `JanusErrors`, `SessionMiddleware` | the types of what `janusErrors()` and `session()` make |
package/dist/device.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ResponseSettings } from '@alxia/core';
2
- /** `@nxgt/janus-hono`'s name for it, so a device remembered by one is by the other. */
2
+ /** `@softistx/janus-hono`'s name for it, so a device remembered by one is by the other. */
3
3
  export declare const DEVICE_COOKIE = "janus-device";
4
4
  export interface DeviceCookieOptions {
5
5
  readonly name?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,uFAAuF;AACvF,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAI5C,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,2FAA2F;AAC3F,wBAAgB,QAAQ,CACvB,GAAG,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,EAClC,OAAO,GAAE,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAM,GAC7C,MAAM,GAAG,IAAI,CAIf;AAED,6EAA6E;AAC7E,wBAAgB,UAAU,CACzB,GAAG,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,gBAAgB,CAAA;CAAE,EACvC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,mBAAwB,GAC/B,IAAI,CASN"}
1
+ {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,2FAA2F;AAC3F,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAI5C,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,2FAA2F;AAC3F,wBAAgB,QAAQ,CACvB,GAAG,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,EAClC,OAAO,GAAE,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAM,GAC7C,MAAM,GAAG,IAAI,CAIf;AAED,6EAA6E;AAC7E,wBAAgB,UAAU,CACzB,GAAG,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,gBAAgB,CAAA;CAAE,EACvC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,mBAAwB,GAC/B,IAAI,CASN"}
package/dist/errors.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { type BaseContext, type Middleware, type Next, type Problem, type Reply } from '@alxia/core';
2
- import { JanusError, type JanusErrorCode, type JanusErrorStatus, statusOf } from '@nxgt/janus';
2
+ import { JanusError, type JanusErrorCode, type JanusErrorStatus, statusOf } from '@softistx/janus';
3
3
  /**
4
4
  * The body a refusal is answered with: its `code`, and only what the client
5
5
  * can act on. Never `reason`, `login`, a hash prefix or a cause: those are
6
- * for your logs. `@nxgt/janus-hono`'s `bodyOf`, kept twice on purpose.
6
+ * for your logs. `@softistx/janus-hono`'s `bodyOf`, kept twice on purpose.
7
7
  */
8
8
  export interface JanusErrorBody {
9
9
  readonly code: JanusErrorCode;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,WAAW,EAGhB,KAAK,UAAU,EAEf,KAAK,IAAI,EACT,KAAK,OAAO,EAGZ,KAAK,KAAK,EACV,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,QAAQ,EACR,MAAM,aAAa,CAAC;AAErB;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CA0BxD;AAED,MAAM,WAAW,kBAAkB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC;CACnE;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;AAE1E,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG,UAAU,CACnC,MAAM,EACN,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,gBAAgB,EAAE,cAAc,GAAG,iBAAiB,CAAC,CAAC,CAC3E,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,WAAW,CASzE;AA2BD,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,WAAW,EAGhB,KAAK,UAAU,EAEf,KAAK,IAAI,EACT,KAAK,OAAO,EAGZ,KAAK,KAAK,EACV,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,QAAQ,EACR,MAAM,iBAAiB,CAAC;AAEzB;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CA0BxD;AAED,MAAM,WAAW,kBAAkB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC;CACnE;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;AAE1E,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG,UAAU,CACnC,MAAM,EACN,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,gBAAgB,EAAE,cAAc,GAAG,iBAAiB,CAAC,CAAC,CAC3E,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,WAAW,CASzE;AA2BD,OAAO,EAAE,QAAQ,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  import {
28
28
  JanusError,
29
29
  statusOf
30
- } from "@nxgt/janus";
30
+ } from "@softistx/janus";
31
31
  function bodyOf(error) {
32
32
  if (error.code === "MAIL_THROTTLED") {
33
33
  return error.retryAfter === undefined ? { code: error.code } : { code: error.code, retryAfter: error.retryAfter };
@@ -223,5 +223,5 @@ export {
223
223
  statusOf
224
224
  };
225
225
 
226
- //# debugId=CC7FB948F0D1EAE664756E2164756E21
226
+ //# debugId=6AAB6689598F45D564756E2164756E21
227
227
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -2,15 +2,15 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/device.ts", "../src/errors.ts", "../src/permission.ts", "../src/refused.ts", "../src/send.ts", "../src/session.ts", "../src/lookup.ts"],
4
4
  "sourcesContent": [
5
- "import type { ResponseSettings } from '@alxia/core';\n\n/** `@nxgt/janus-hono`'s name for it, so a device remembered by one is by the other. */\nexport const DEVICE_COOKIE = 'janus-device';\n\nconst MAX_AGE = 400 * 24 * 60 * 60;\n\nexport interface DeviceCookieOptions {\n\treadonly name?: string;\n\treadonly domain?: string;\n\treadonly path?: string;\n\treadonly sameSite?: 'lax' | 'strict' | 'none';\n\treadonly secure?: boolean;\n\t/** Seconds. Four hundred days, the most a browser keeps, by default. */\n\treadonly maxAge?: number;\n}\n\n/** The device token the request carries, for `signIn(…, { device })`: `null` when none. */\nexport function deviceOf(\n\tctx: { readonly request: Request },\n\toptions: Pick<DeviceCookieOptions, 'name'> = {},\n): string | null {\n\tconst cookies = new Bun.CookieMap(ctx.request.headers.get('cookie') ?? '');\n\tconst value = cookies.get(options.name ?? DEVICE_COOKIE);\n\treturn value === null || value === '' ? null : value;\n}\n\n/** Sets the device cookie, or sets it again so it lasts another `maxAge`. */\nexport function sendDevice(\n\tctx: { readonly set: ResponseSettings },\n\ttoken: string,\n\toptions: DeviceCookieOptions = {},\n): void {\n\tctx.set.cookies.set(options.name ?? DEVICE_COOKIE, token, {\n\t\thttpOnly: true,\n\t\tpath: options.path ?? '/',\n\t\tsameSite: options.sameSite ?? 'lax',\n\t\tsecure: options.secure ?? true,\n\t\tmaxAge: options.maxAge ?? MAX_AGE,\n\t\t...(options.domain === undefined ? {} : { domain: options.domain }),\n\t});\n}\n",
6
- "import {\n\ttype BaseContext,\n\tdefineMiddleware,\n\terrorFormat,\n\ttype Middleware,\n\tmarkFactory,\n\ttype Next,\n\ttype Problem,\n\tproblem,\n\tproblemOf,\n\ttype Reply,\n} from '@alxia/core';\nimport {\n\tJanusError,\n\ttype JanusErrorCode,\n\ttype JanusErrorStatus,\n\tstatusOf,\n} from '@nxgt/janus';\n\n/**\n * The body a refusal is answered with: its `code`, and only what the client\n * can act on. Never `reason`, `login`, a hash prefix or a cause: those are\n * for your logs. `@nxgt/janus-hono`'s `bodyOf`, kept twice on purpose.\n */\nexport interface JanusErrorBody {\n\treadonly code: JanusErrorCode;\n\treadonly issues?: JanusError['issues'];\n\treadonly minLength?: number;\n\treadonly attemptsLeft?: number;\n\treadonly retryAfter?: number;\n}\n\nexport function bodyOf(error: JanusError): JanusErrorBody {\n\t// `MAIL_THROTTLED` (429) exists from @nxgt/janus 0.18: compared as a\n\t// string so that the range still admits 0.17, where the code is not typed.\n\tif ((error.code as string) === 'MAIL_THROTTLED') {\n\t\treturn error.retryAfter === undefined\n\t\t\t? { code: error.code }\n\t\t\t: { code: error.code, retryAfter: error.retryAfter };\n\t}\n\tswitch (error.code) {\n\t\tcase 'USER_INVALID':\n\t\t\treturn { code: error.code, issues: error.issues ?? [] };\n\t\tcase 'PASSWORD_TOO_SHORT':\n\t\t\treturn error.minLength === undefined\n\t\t\t\t? { code: error.code }\n\t\t\t\t: { code: error.code, minLength: error.minLength };\n\t\tcase 'CODE_INVALID':\n\t\t\treturn error.attemptsLeft === undefined\n\t\t\t\t? { code: error.code }\n\t\t\t\t: { code: error.code, attemptsLeft: error.attemptsLeft };\n\t\tcase 'CREDENTIALS_INVALID':\n\t\t\treturn error.retryAfter === undefined\n\t\t\t\t? { code: error.code }\n\t\t\t\t: { code: error.code, retryAfter: error.retryAfter };\n\t\tdefault:\n\t\t\treturn { code: error.code };\n\t}\n}\n\nexport interface JanusErrorsOptions {\n\t/**\n\t * Called with every `JanusError` answered 5xx — `STORE_FAILED` and the\n\t * like, the server's to fix — as it is answered: started, not awaited,\n\t * so a slow report never holds the response. It cannot change the\n\t * answer: one that throws or rejects is a warning.\n\t */\n\treadonly report?: (error: JanusError, ctx: BaseContext) => unknown;\n}\n\n/**\n * A `JanusError` answered under `alxia({ errors: 'problem' })`: an RFC\n * 9457 problem, its `code` and the members of `JanusErrorBody` its\n * extensions.\n */\nexport type JanusErrorProblem = Problem<JanusErrorStatus, JanusErrorBody>;\n\n/** What `janusErrors()` makes: a middleware that answers what janus throws behind it. */\nexport type JanusErrors = Middleware<\n\tobject,\n\tPromise<Next | Reply<JanusErrorStatus, JanusErrorBody | JanusErrorProblem>>\n>;\n\n/**\n * Janus's errors answered, as a middleware: every `JanusError` thrown\n * behind it — by `session()`, `permission()`, a route — a sign-in\n * refused, a login taken, a store down — is answered with janus's status\n * and `bodyOf(error)` — under `alxia({ errors: 'problem' })`, a problem\n * whose extensions are that body — typed on the routes declared after it. A throttled\n * sign-in or e-mail (`MAIL_THROTTLED`) carries `Retry-After`. Anything else goes on, thrown, to the\n * middlewares before it, or to the route boundary's 500.\n *\n * Give it to `use` before `session()`, so that a store down while the\n * session is read is answered too:\n *\n * ```ts\n * app.use(janusErrors(), session(accounts)).post('/sign-in', ...);\n * ```\n *\n * **`STORE_FAILED` is a 503**, never a 401 or a 404: an outage is not an\n * answer.\n */\nexport function janusErrors(options: JanusErrorsOptions = {}): JanusErrors {\n\treturn defineMiddleware(async function janusErrors(ctx, next) {\n\t\ttry {\n\t\t\treturn await next();\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof JanusError)) throw error;\n\t\t\treturn answer(error, ctx, options);\n\t\t}\n\t});\n}\n\n/** A `JanusError`, answered: its status, its body, reported when a 5xx. */\nfunction answer(\n\terror: JanusError,\n\tctx: BaseContext,\n\toptions: JanusErrorsOptions,\n): Reply<JanusErrorStatus, JanusErrorBody | JanusErrorProblem> {\n\tconst status: JanusErrorStatus = statusOf(error.code);\n\tif (status >= 500 && options.report !== undefined) {\n\t\tPromise.resolve()\n\t\t\t.then(() => options.report?.(error, ctx))\n\t\t\t.catch((failure: unknown) =>\n\t\t\t\tprocess.emitWarning(`janusErrors(): report failed: ${String(failure)}`),\n\t\t\t);\n\t}\n\tconst body = bodyOf(error);\n\tconst init =\n\t\terror.retryAfter === undefined\n\t\t\t? {}\n\t\t\t: { headers: { 'retry-after': String(error.retryAfter) } };\n\tif (errorFormat(ctx) === 'problem') {\n\t\treturn problem(problemOf(ctx, { status, ...body }), init);\n\t}\n\treturn ctx.reply(status, body, init);\n}\n\nexport { statusOf };\n\nmarkFactory(janusErrors);\n",
7
- "import {\n\ttype BaseContext,\n\tdefineMiddleware,\n\tmarkFactory,\n\ttype Problem,\n\ttype RequiresOf,\n} from '@alxia/core';\nimport type {\n\tCheckableOf,\n\tModelConfig,\n\tObjectTypeOf,\n\tPermissions,\n\tSubjectRef,\n} from '@nxgt/janus/permissions';\nimport type { Awaitable, ObjectData, OptionsArgs } from './permission-options';\nimport { refused } from './refused';\n\n/** The body of each refusal. */\nexport interface PermissionRefusedBody {\n\treadonly error: 'unauthenticated' | 'not_found' | 'forbidden';\n}\n\n/** A refusal under `alxia({ errors: 'problem' })`: its 401, 404 or 403 as a problem. */\nexport type PermissionRefusedProblem = Problem<401 | 403 | 404>;\n\ntype LooseCan = (\n\tsubject: SubjectRef<ModelConfig> | null,\n\tpermission: string,\n\tobject: { readonly type: string; readonly id: string },\n\toptions?: { readonly ctx: unknown },\n) => Promise<boolean>;\n\n/**\n * A guard, as a middleware: the routes declared after it run only if the\n * subject holds `permission` on an object of `type`. It loads the object\n * once, checks it with `access.can`, and hands it to them as `object`.\n *\n * | the request | answered |\n * | --- | --- |\n * | anonymous | 401 — before the object is loaded |\n * | `load` answers `null` | 404 |\n * | a denial | 403 |\n * | allowed | the route runs, `object` set |\n *\n * Each refusal is typed on those routes. **A failure throws**: a store that\n * cannot answer is `STORE_FAILED`, never a 403 — `janusErrors()` answers\n * it 503. Scope it with `group`, so it guards only its routes — given to\n * the app's `use`, it would refuse a request no route matches too:\n *\n * ```ts\n * app.use(janusErrors(), session(accounts)).group('/records/:id', (records) =>\n * records.use(permission(access, 'view', 'record', byParam('id', findRecord)))\n * .get('/', ({ object, reply }) => reply(200, object)));\n * ```\n */\nexport function permission<\n\tC extends ModelConfig,\n\tconst T extends ObjectTypeOf<C>,\n\tconst P extends CheckableOf<C, T>,\n\tO extends ObjectData<C, T>,\n\tLoadCtx extends object = BaseContext,\n\tSubjectCtx extends object = BaseContext,\n\tCheckCtx extends object = BaseContext,\n>(\n\taccess: Pick<Permissions<C>, 'can'>,\n\tpermission: P,\n\ttype: T,\n\tload: (ctx: BaseContext & LoadCtx) => Awaitable<O | null>,\n\t...options: OptionsArgs<C, T, P, O, SubjectCtx, CheckCtx>\n) {\n\t// `app.use` has checked that the app gives what `load`, `subject` and `ctx` read.\n\tconst loadOf = load as (ctx: BaseContext) => Awaitable<O | null>;\n\tconst { subject, ctx: ctxOf } = (options[0] ?? {}) as {\n\t\treadonly subject?: (\n\t\t\tctx: BaseContext,\n\t\t) => Awaitable<SubjectRef<ModelConfig> | null>;\n\t\treadonly ctx?: (ctx: BaseContext, object: unknown) => Awaitable<unknown>;\n\t};\n\tconst can = access.can as LooseCan;\n\tconst refuse = <Status extends 401 | 403 | 404>(\n\t\tctx: BaseContext,\n\t\tstatus: Status,\n\t\terror: PermissionRefusedBody['error'],\n\t\tdetail: string,\n\t) => refused(ctx, status, { error } as PermissionRefusedBody, detail);\n\treturn defineMiddleware<\n\t\tRequiresOf<\n\t\t\tLoadCtx & SubjectCtx & CheckCtx,\n\t\t\tAnnotatedAny<LoadCtx, SubjectCtx>\n\t\t>\n\t>()(async function permissionGuard(ctx, next) {\n\t\tconst who = subject === undefined ? userOf(ctx) : await subject(ctx);\n\t\tif (who === null) {\n\t\t\treturn refuse(ctx, 401, 'unauthenticated', 'The request has no session');\n\t\t}\n\t\tconst object = await loadOf(ctx);\n\t\tif (object === null) {\n\t\t\treturn refuse(ctx, 404, 'not_found', `No ${type} is found here`);\n\t\t}\n\t\tconst allowed = await can(\n\t\t\twho,\n\t\t\tpermission,\n\t\t\tview(object, type),\n\t\t\tctxOf === undefined ? undefined : { ctx: await ctxOf(ctx, object) },\n\t\t);\n\t\tif (!allowed) {\n\t\t\treturn refuse(\n\t\t\t\tctx,\n\t\t\t\t403,\n\t\t\t\t'forbidden',\n\t\t\t\t`The ${permission} permission on this ${type} is not granted`,\n\t\t\t);\n\t\t}\n\t\treturn next({ object });\n\t});\n}\n\n/**\n * The callback to name when `RequiresOf` refuses one annotated `any`: `load`\n * or `subject` when it is that one, `ctx` otherwise. An `any` in any of the\n * three makes their intersection `any`, so `RequiresOf` cannot tell which;\n * a `Record<string, any>` passes the same test.\n */\ntype AnnotatedAny<LoadCtx, SubjectCtx> = [LoadCtx] extends [\n\t{ readonly '~any': 1 },\n]\n\t? 'load'\n\t: [SubjectCtx] extends [{ readonly '~any': 1 }]\n\t\t? 'subject'\n\t\t: 'ctx';\n\n/**\n * A `load` reading one path parameter, as it arrived: `find(id)`, or `null`\n * — a 404 — when the route has no such parameter.\n */\nexport function byParam<O>(\n\tname: string,\n\tfind: (id: string) => Awaitable<O | null>,\n): (ctx: BaseContext) => Awaitable<O | null> {\n\treturn (ctx) => {\n\t\tconst id = ctx.pathParams[name];\n\t\treturn id === undefined ? null : find(id);\n\t};\n}\n\n/** The object as `can()` reads it: `type` added, every other field read from the object itself. */\nfunction view(\n\tobject: object,\n\ttype: string,\n): { readonly type: string; readonly id: string } {\n\treturn new Proxy(object, {\n\t\tget: (target, key) => (key === 'type' ? type : Reflect.get(target, key)),\n\t\thas: (target, key) => key === 'type' || Reflect.has(target, key),\n\t}) as { readonly type: string; readonly id: string };\n}\n\n/** The `user` `session()` derived — or a wiring error when nothing did. */\nfunction userOf(ctx: BaseContext): SubjectRef<ModelConfig> | null {\n\tconst user = (ctx as { readonly user?: unknown }).user;\n\tif (user === undefined) {\n\t\tthrow new TypeError(\n\t\t\t'permission(): no user in the context — use session(auth) before it, or pass { subject }',\n\t\t);\n\t}\n\treturn user as SubjectRef<ModelConfig> | null;\n}\n\nmarkFactory(permission);\n",
5
+ "import type { ResponseSettings } from '@alxia/core';\n\n/** `@softistx/janus-hono`'s name for it, so a device remembered by one is by the other. */\nexport const DEVICE_COOKIE = 'janus-device';\n\nconst MAX_AGE = 400 * 24 * 60 * 60;\n\nexport interface DeviceCookieOptions {\n\treadonly name?: string;\n\treadonly domain?: string;\n\treadonly path?: string;\n\treadonly sameSite?: 'lax' | 'strict' | 'none';\n\treadonly secure?: boolean;\n\t/** Seconds. Four hundred days, the most a browser keeps, by default. */\n\treadonly maxAge?: number;\n}\n\n/** The device token the request carries, for `signIn(…, { device })`: `null` when none. */\nexport function deviceOf(\n\tctx: { readonly request: Request },\n\toptions: Pick<DeviceCookieOptions, 'name'> = {},\n): string | null {\n\tconst cookies = new Bun.CookieMap(ctx.request.headers.get('cookie') ?? '');\n\tconst value = cookies.get(options.name ?? DEVICE_COOKIE);\n\treturn value === null || value === '' ? null : value;\n}\n\n/** Sets the device cookie, or sets it again so it lasts another `maxAge`. */\nexport function sendDevice(\n\tctx: { readonly set: ResponseSettings },\n\ttoken: string,\n\toptions: DeviceCookieOptions = {},\n): void {\n\tctx.set.cookies.set(options.name ?? DEVICE_COOKIE, token, {\n\t\thttpOnly: true,\n\t\tpath: options.path ?? '/',\n\t\tsameSite: options.sameSite ?? 'lax',\n\t\tsecure: options.secure ?? true,\n\t\tmaxAge: options.maxAge ?? MAX_AGE,\n\t\t...(options.domain === undefined ? {} : { domain: options.domain }),\n\t});\n}\n",
6
+ "import {\n\ttype BaseContext,\n\tdefineMiddleware,\n\terrorFormat,\n\ttype Middleware,\n\tmarkFactory,\n\ttype Next,\n\ttype Problem,\n\tproblem,\n\tproblemOf,\n\ttype Reply,\n} from '@alxia/core';\nimport {\n\tJanusError,\n\ttype JanusErrorCode,\n\ttype JanusErrorStatus,\n\tstatusOf,\n} from '@softistx/janus';\n\n/**\n * The body a refusal is answered with: its `code`, and only what the client\n * can act on. Never `reason`, `login`, a hash prefix or a cause: those are\n * for your logs. `@softistx/janus-hono`'s `bodyOf`, kept twice on purpose.\n */\nexport interface JanusErrorBody {\n\treadonly code: JanusErrorCode;\n\treadonly issues?: JanusError['issues'];\n\treadonly minLength?: number;\n\treadonly attemptsLeft?: number;\n\treadonly retryAfter?: number;\n}\n\nexport function bodyOf(error: JanusError): JanusErrorBody {\n\t// `MAIL_THROTTLED` (429) exists from janus 0.18: compared as a\n\t// string so that a janus where the code is not typed still type-checks.\n\tif ((error.code as string) === 'MAIL_THROTTLED') {\n\t\treturn error.retryAfter === undefined\n\t\t\t? { code: error.code }\n\t\t\t: { code: error.code, retryAfter: error.retryAfter };\n\t}\n\tswitch (error.code) {\n\t\tcase 'USER_INVALID':\n\t\t\treturn { code: error.code, issues: error.issues ?? [] };\n\t\tcase 'PASSWORD_TOO_SHORT':\n\t\t\treturn error.minLength === undefined\n\t\t\t\t? { code: error.code }\n\t\t\t\t: { code: error.code, minLength: error.minLength };\n\t\tcase 'CODE_INVALID':\n\t\t\treturn error.attemptsLeft === undefined\n\t\t\t\t? { code: error.code }\n\t\t\t\t: { code: error.code, attemptsLeft: error.attemptsLeft };\n\t\tcase 'CREDENTIALS_INVALID':\n\t\t\treturn error.retryAfter === undefined\n\t\t\t\t? { code: error.code }\n\t\t\t\t: { code: error.code, retryAfter: error.retryAfter };\n\t\tdefault:\n\t\t\treturn { code: error.code };\n\t}\n}\n\nexport interface JanusErrorsOptions {\n\t/**\n\t * Called with every `JanusError` answered 5xx — `STORE_FAILED` and the\n\t * like, the server's to fix — as it is answered: started, not awaited,\n\t * so a slow report never holds the response. It cannot change the\n\t * answer: one that throws or rejects is a warning.\n\t */\n\treadonly report?: (error: JanusError, ctx: BaseContext) => unknown;\n}\n\n/**\n * A `JanusError` answered under `alxia({ errors: 'problem' })`: an RFC\n * 9457 problem, its `code` and the members of `JanusErrorBody` its\n * extensions.\n */\nexport type JanusErrorProblem = Problem<JanusErrorStatus, JanusErrorBody>;\n\n/** What `janusErrors()` makes: a middleware that answers what janus throws behind it. */\nexport type JanusErrors = Middleware<\n\tobject,\n\tPromise<Next | Reply<JanusErrorStatus, JanusErrorBody | JanusErrorProblem>>\n>;\n\n/**\n * Janus's errors answered, as a middleware: every `JanusError` thrown\n * behind it — by `session()`, `permission()`, a route — a sign-in\n * refused, a login taken, a store down — is answered with janus's status\n * and `bodyOf(error)` — under `alxia({ errors: 'problem' })`, a problem\n * whose extensions are that body — typed on the routes declared after it. A throttled\n * sign-in or e-mail (`MAIL_THROTTLED`) carries `Retry-After`. Anything else goes on, thrown, to the\n * middlewares before it, or to the route boundary's 500.\n *\n * Give it to `use` before `session()`, so that a store down while the\n * session is read is answered too:\n *\n * ```ts\n * app.use(janusErrors(), session(accounts)).post('/sign-in', ...);\n * ```\n *\n * **`STORE_FAILED` is a 503**, never a 401 or a 404: an outage is not an\n * answer.\n */\nexport function janusErrors(options: JanusErrorsOptions = {}): JanusErrors {\n\treturn defineMiddleware(async function janusErrors(ctx, next) {\n\t\ttry {\n\t\t\treturn await next();\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof JanusError)) throw error;\n\t\t\treturn answer(error, ctx, options);\n\t\t}\n\t});\n}\n\n/** A `JanusError`, answered: its status, its body, reported when a 5xx. */\nfunction answer(\n\terror: JanusError,\n\tctx: BaseContext,\n\toptions: JanusErrorsOptions,\n): Reply<JanusErrorStatus, JanusErrorBody | JanusErrorProblem> {\n\tconst status: JanusErrorStatus = statusOf(error.code);\n\tif (status >= 500 && options.report !== undefined) {\n\t\tPromise.resolve()\n\t\t\t.then(() => options.report?.(error, ctx))\n\t\t\t.catch((failure: unknown) =>\n\t\t\t\tprocess.emitWarning(`janusErrors(): report failed: ${String(failure)}`),\n\t\t\t);\n\t}\n\tconst body = bodyOf(error);\n\tconst init =\n\t\terror.retryAfter === undefined\n\t\t\t? {}\n\t\t\t: { headers: { 'retry-after': String(error.retryAfter) } };\n\tif (errorFormat(ctx) === 'problem') {\n\t\treturn problem(problemOf(ctx, { status, ...body }), init);\n\t}\n\treturn ctx.reply(status, body, init);\n}\n\nexport { statusOf };\n\nmarkFactory(janusErrors);\n",
7
+ "import {\n\ttype BaseContext,\n\tdefineMiddleware,\n\tmarkFactory,\n\ttype Problem,\n\ttype RequiresOf,\n} from '@alxia/core';\nimport type {\n\tCheckableOf,\n\tModelConfig,\n\tObjectTypeOf,\n\tPermissions,\n\tSubjectRef,\n} from '@softistx/janus/permissions';\nimport type { Awaitable, ObjectData, OptionsArgs } from './permission-options';\nimport { refused } from './refused';\n\n/** The body of each refusal. */\nexport interface PermissionRefusedBody {\n\treadonly error: 'unauthenticated' | 'not_found' | 'forbidden';\n}\n\n/** A refusal under `alxia({ errors: 'problem' })`: its 401, 404 or 403 as a problem. */\nexport type PermissionRefusedProblem = Problem<401 | 403 | 404>;\n\ntype LooseCan = (\n\tsubject: SubjectRef<ModelConfig> | null,\n\tpermission: string,\n\tobject: { readonly type: string; readonly id: string },\n\toptions?: { readonly ctx: unknown },\n) => Promise<boolean>;\n\n/**\n * A guard, as a middleware: the routes declared after it run only if the\n * subject holds `permission` on an object of `type`. It loads the object\n * once, checks it with `access.can`, and hands it to them as `object`.\n *\n * | the request | answered |\n * | --- | --- |\n * | anonymous | 401 — before the object is loaded |\n * | `load` answers `null` | 404 |\n * | a denial | 403 |\n * | allowed | the route runs, `object` set |\n *\n * Each refusal is typed on those routes. **A failure throws**: a store that\n * cannot answer is `STORE_FAILED`, never a 403 — `janusErrors()` answers\n * it 503. Scope it with `group`, so it guards only its routes — given to\n * the app's `use`, it would refuse a request no route matches too:\n *\n * ```ts\n * app.use(janusErrors(), session(accounts)).group('/records/:id', (records) =>\n * records.use(permission(access, 'view', 'record', byParam('id', findRecord)))\n * .get('/', ({ object, reply }) => reply(200, object)));\n * ```\n */\nexport function permission<\n\tC extends ModelConfig,\n\tconst T extends ObjectTypeOf<C>,\n\tconst P extends CheckableOf<C, T>,\n\tO extends ObjectData<C, T>,\n\tLoadCtx extends object = BaseContext,\n\tSubjectCtx extends object = BaseContext,\n\tCheckCtx extends object = BaseContext,\n>(\n\taccess: Pick<Permissions<C>, 'can'>,\n\tpermission: P,\n\ttype: T,\n\tload: (ctx: BaseContext & LoadCtx) => Awaitable<O | null>,\n\t...options: OptionsArgs<C, T, P, O, SubjectCtx, CheckCtx>\n) {\n\t// `app.use` has checked that the app gives what `load`, `subject` and `ctx` read.\n\tconst loadOf = load as (ctx: BaseContext) => Awaitable<O | null>;\n\tconst { subject, ctx: ctxOf } = (options[0] ?? {}) as {\n\t\treadonly subject?: (\n\t\t\tctx: BaseContext,\n\t\t) => Awaitable<SubjectRef<ModelConfig> | null>;\n\t\treadonly ctx?: (ctx: BaseContext, object: unknown) => Awaitable<unknown>;\n\t};\n\tconst can = access.can as LooseCan;\n\tconst refuse = <Status extends 401 | 403 | 404>(\n\t\tctx: BaseContext,\n\t\tstatus: Status,\n\t\terror: PermissionRefusedBody['error'],\n\t\tdetail: string,\n\t) => refused(ctx, status, { error } as PermissionRefusedBody, detail);\n\treturn defineMiddleware<\n\t\tRequiresOf<\n\t\t\tLoadCtx & SubjectCtx & CheckCtx,\n\t\t\tAnnotatedAny<LoadCtx, SubjectCtx>\n\t\t>\n\t>()(async function permissionGuard(ctx, next) {\n\t\tconst who = subject === undefined ? userOf(ctx) : await subject(ctx);\n\t\tif (who === null) {\n\t\t\treturn refuse(ctx, 401, 'unauthenticated', 'The request has no session');\n\t\t}\n\t\tconst object = await loadOf(ctx);\n\t\tif (object === null) {\n\t\t\treturn refuse(ctx, 404, 'not_found', `No ${type} is found here`);\n\t\t}\n\t\tconst allowed = await can(\n\t\t\twho,\n\t\t\tpermission,\n\t\t\tview(object, type),\n\t\t\tctxOf === undefined ? undefined : { ctx: await ctxOf(ctx, object) },\n\t\t);\n\t\tif (!allowed) {\n\t\t\treturn refuse(\n\t\t\t\tctx,\n\t\t\t\t403,\n\t\t\t\t'forbidden',\n\t\t\t\t`The ${permission} permission on this ${type} is not granted`,\n\t\t\t);\n\t\t}\n\t\treturn next({ object });\n\t});\n}\n\n/**\n * The callback to name when `RequiresOf` refuses one annotated `any`: `load`\n * or `subject` when it is that one, `ctx` otherwise. An `any` in any of the\n * three makes their intersection `any`, so `RequiresOf` cannot tell which;\n * a `Record<string, any>` passes the same test.\n */\ntype AnnotatedAny<LoadCtx, SubjectCtx> = [LoadCtx] extends [\n\t{ readonly '~any': 1 },\n]\n\t? 'load'\n\t: [SubjectCtx] extends [{ readonly '~any': 1 }]\n\t\t? 'subject'\n\t\t: 'ctx';\n\n/**\n * A `load` reading one path parameter, as it arrived: `find(id)`, or `null`\n * — a 404 — when the route has no such parameter.\n */\nexport function byParam<O>(\n\tname: string,\n\tfind: (id: string) => Awaitable<O | null>,\n): (ctx: BaseContext) => Awaitable<O | null> {\n\treturn (ctx) => {\n\t\tconst id = ctx.pathParams[name];\n\t\treturn id === undefined ? null : find(id);\n\t};\n}\n\n/** The object as `can()` reads it: `type` added, every other field read from the object itself. */\nfunction view(\n\tobject: object,\n\ttype: string,\n): { readonly type: string; readonly id: string } {\n\treturn new Proxy(object, {\n\t\tget: (target, key) => (key === 'type' ? type : Reflect.get(target, key)),\n\t\thas: (target, key) => key === 'type' || Reflect.has(target, key),\n\t}) as { readonly type: string; readonly id: string };\n}\n\n/** The `user` `session()` derived — or a wiring error when nothing did. */\nfunction userOf(ctx: BaseContext): SubjectRef<ModelConfig> | null {\n\tconst user = (ctx as { readonly user?: unknown }).user;\n\tif (user === undefined) {\n\t\tthrow new TypeError(\n\t\t\t'permission(): no user in the context — use session(auth) before it, or pass { subject }',\n\t\t);\n\t}\n\treturn user as SubjectRef<ModelConfig> | null;\n}\n\nmarkFactory(permission);\n",
8
8
  "/**\n * A refusal of `session()` or `permission()`, in the format of the app\n * that serves the request: its `{ error }` body by default, an RFC 9457\n * problem under `alxia({ errors: 'problem' })`.\n */\nimport {\n\ttype BaseContext,\n\terrorFormat,\n\ttype Problem,\n\tproblem,\n\tproblemOf,\n\ttype Reply,\n} from '@alxia/core';\n\n/** `body` with `status`, or the problem of `status` saying `detail`. */\nexport function refused<Status extends 401 | 403 | 404, Body>(\n\tctx: BaseContext,\n\tstatus: Status,\n\tbody: Body,\n\tdetail: string,\n): Reply<Status, Body | Problem<Status>> {\n\tif (errorFormat(ctx) === 'problem') {\n\t\treturn problem(problemOf(ctx, { status, detail }));\n\t}\n\treturn ctx.reply(status, body);\n}\n",
9
9
  "import type { ResponseSettings } from '@alxia/core';\nimport { type DeviceCookieOptions, sendDevice } from './device';\nimport type { Auth, SessionOpened } from './types';\n\n/** What `sendSession` takes besides: the device cookie's options. */\nexport interface SendSessionOptions {\n\treadonly device?: DeviceCookieOptions;\n}\n\n/**\n * Sends the session cookie — after `signUp`, `signIn`, anything that\n * answered a token and its session — and answers the user. The token is in\n * the cookie, never in the body. With a `deviceToken`, the device cookie\n * too.\n *\n * ```ts\n * const signedIn = await auth.signIn(body, { device: deviceOf(ctx) });\n * return reply(200, { id: sendSession(ctx, auth, signedIn).id });\n * ```\n */\nexport function sendSession<U>(\n\tctx: { readonly set: ResponseSettings },\n\tauth: Pick<Auth<{ readonly type: string }>, 'cookie'>,\n\tsignedIn: SessionOpened<U>,\n\toptions: SendSessionOptions = {},\n): U {\n\tctx.set.headers.append(\n\t\t'set-cookie',\n\t\tauth.cookie.serialize(signedIn.token, signedIn.session),\n\t);\n\tif (typeof signedIn.deviceToken === 'string') {\n\t\tsendDevice(ctx, signedIn.deviceToken, options.device);\n\t}\n\treturn signedIn.user;\n}\n\n/**\n * Revokes the session the request presents and clears the cookie — cleared\n * whatever the answer, so a browser holding a stale cookie drops it too.\n */\nexport async function signOut(\n\tctx: { readonly request: Request; readonly set: ResponseSettings },\n\tauth: Pick<Auth<{ readonly type: string }>, 'signOut' | 'cookie'>,\n): Promise<boolean> {\n\tconst revoked = await auth.signOut(ctx.request);\n\tctx.set.headers.append('set-cookie', auth.cookie.clear());\n\treturn revoked;\n}\n",
10
- "import {\n\tdefineMiddleware,\n\ttype Empty,\n\ttype Middleware,\n\tmarkFactory,\n\ttype Next,\n\ttype Problem,\n\ttype Reply,\n\tsettle,\n\twithHeaders,\n} from '@alxia/core';\nimport type { Session } from '@nxgt/janus';\nimport { type DeviceCookieOptions, deviceOf } from './device';\nimport { authenticateOnce, type Found } from './lookup';\nimport { refused } from './refused';\nimport { sendSession, signOut } from './send';\nimport type { Auth, RequestAuth, UserOfAuth } from './types';\n\nexport interface SessionOptions<T extends string> {\n\t/** Only a user of this type is authenticated here; any other is anonymous. */\n\treadonly type?: T;\n\t/**\n\t * `true`: an anonymous request is answered 401 and the route never runs,\n\t * so `user` is never `null` in it — and the 401 is in its type. A\n\t * `boolean` known only at runtime keeps the 401 in the type, and `user`\n\t * may be `null`.\n\t */\n\treadonly required?: boolean;\n\t/** The device cookie `auth.device` reads and `auth.send` sets. */\n\treadonly device?: DeviceCookieOptions;\n}\n\n/** The body of the 401 a required session answers. */\nexport interface UnauthenticatedBody {\n\treadonly error: 'unauthenticated';\n}\n\n/** The 401 a required session answers under `alxia({ errors: 'problem' })`. */\nexport type UnauthenticatedProblem = Problem<401>;\n\ntype UserOf<A, T> = Extract<UserOfAuth<A>, { readonly type: T }>;\n\n/** What a session gives the routes after it. */\ntype Given<User, S> = {\n\treadonly user: User;\n\treadonly session: S;\n\treadonly auth: RequestAuth;\n};\n\n/** What `session()` makes: a middleware that gives `Added`, or answers `Refused`. */\nexport type SessionMiddleware<\n\tAdded extends object,\n\tRefused = never,\n> = Middleware<Empty, Promise<Refused | Next<Added>>>;\n\n/**\n * Who a request belongs to, as a middleware: `auth.authenticate(request)`,\n * and the routes declared after it read `user` and `session` — typed by\n * the user schema, narrowed by `type`. Given to `app.use`, it runs on a\n * request no route matches too: a required session answers it 401 before\n * its 404.\n *\n * **An outage is not anonymous**: a store that cannot answer throws\n * `STORE_FAILED`, which `janusErrors()` — given to `use` before it —\n * answers 503, never 401.\n *\n * A session renewed in passing is sent again as a cookie, after the route —\n * only to a request that presented it as one (a `Bearer` client is never\n * handed a cookie), and never over a session cookie the route set itself.\n *\n * ```ts\n * app.use(janusErrors(), session(accounts, { required: true }))\n * .get('/me', ({ user, reply }) => reply.ok(user));\n * ```\n */\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(\n\tauth: A,\n\toptions: SessionOptions<T> & { readonly required: true },\n): SessionMiddleware<\n\tGiven<UserOf<A, T>, Session>,\n\tReply<401, UnauthenticatedBody | UnauthenticatedProblem>\n>;\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(\n\tauth: A,\n\toptions?: SessionOptions<T> & { readonly required?: false },\n): SessionMiddleware<Given<UserOf<A, T> | null, Session | null>>;\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(\n\tauth: A,\n\toptions?: SessionOptions<T>,\n): SessionMiddleware<\n\tGiven<UserOf<A, T> | null, Session | null>,\n\tReply<401, UnauthenticatedBody | UnauthenticatedProblem>\n>;\nexport function session(\n\tauth: Auth<{ readonly type: string }>,\n\toptions: SessionOptions<string> = {},\n): unknown {\n\tconst unauthenticated: UnauthenticatedBody = { error: 'unauthenticated' };\n\treturn defineMiddleware(async function session(ctx, next) {\n\t\tconst { request } = ctx;\n\t\tconst found = await authenticateOnce(auth, request, options.type);\n\t\tif (found === null && options.required === true) {\n\t\t\treturn refused(ctx, 401, unauthenticated, 'The request has no session');\n\t\t}\n\t\tconst bound: RequestAuth = {\n\t\t\tdevice: deviceOf(ctx, options.device),\n\t\t\tsend: (signedIn) => sendSession(ctx, auth, signedIn, options),\n\t\t\tsignOut: () => signOut(ctx, auth),\n\t\t};\n\t\t// Settled: a refusal's 400, an error's answer, still carry the renewal.\n\t\tconst response = await settle(\n\t\t\tctx,\n\t\t\tnext({\n\t\t\t\tuser: found?.user ?? null,\n\t\t\t\tsession: found?.session ?? null,\n\t\t\t\tauth: bound,\n\t\t\t}),\n\t\t);\n\t\treturn found?.renewed === true\n\t\t\t? renewed(auth, request, found, response)\n\t\t\t: response;\n\t});\n}\n\n/**\n * A session renewed in passing, sent again as a cookie: only to a request\n * that presented it as one, and never over a cookie the route set itself —\n * a sign-in or a sign-out behind it must not be undone by the session it\n * replaced.\n */\nfunction renewed<R extends Response>(\n\tauth: Auth<{ readonly type: string }>,\n\trequest: Request,\n\tfound: NonNullable<Found>,\n\tresponse: R,\n): R {\n\tconst name = `${auth.cookie.name}=`;\n\tconst presented = new Bun.CookieMap(request.headers.get('cookie') ?? '').get(\n\t\tauth.cookie.name,\n\t);\n\tif (\n\t\tpresented !== found.token ||\n\t\tresponse.headers.getSetCookie().some((value) => value.startsWith(name))\n\t) {\n\t\treturn response;\n\t}\n\treturn withHeaders(response, (headers) =>\n\t\theaders.append(\n\t\t\t'set-cookie',\n\t\t\tauth.cookie.serialize(found.token, found.session),\n\t\t),\n\t) as R;\n}\n\nmarkFactory(session);\n",
11
- "import type { Authenticated } from '@nxgt/janus';\nimport type { Auth } from './types';\n\nexport type Found = Authenticated<{ readonly type: string }> | null;\n\n/**\n * Each request's lookups, by instance and by `type`: an open `session()`\n * and a required one behind it, of the same `type`, look the session up\n * once. They live as long as the `Request` object; a lookup that failed is\n * forgotten, so the next dispatch of that object asks again.\n */\nconst lookups = new WeakMap<\n\tobject,\n\tWeakMap<Request, Map<string, Promise<Found>>>\n>();\n\nexport function authenticateOnce(\n\tauth: Auth<{ readonly type: string }>,\n\trequest: Request,\n\ttype: string | undefined,\n): Promise<Found> {\n\tlet byRequest = lookups.get(auth);\n\tif (byRequest === undefined) {\n\t\tbyRequest = new WeakMap();\n\t\tlookups.set(auth, byRequest);\n\t}\n\tlet byType = byRequest.get(request);\n\tif (byType === undefined) {\n\t\tbyType = new Map();\n\t\tbyRequest.set(request, byType);\n\t}\n\tconst key = type ?? '';\n\tlet found = byType.get(key);\n\tif (found === undefined) {\n\t\tfound = auth.authenticate(\n\t\t\trequest,\n\t\t\ttype === undefined ? undefined : { type },\n\t\t);\n\t\tbyType.set(key, found);\n\t\tconst asked = found;\n\t\tasked.catch(() => {\n\t\t\tif (byType.get(key) === asked) byType.delete(key);\n\t\t});\n\t}\n\treturn found;\n}\n"
10
+ "import {\n\tdefineMiddleware,\n\ttype Empty,\n\ttype Middleware,\n\tmarkFactory,\n\ttype Next,\n\ttype Problem,\n\ttype Reply,\n\tsettle,\n\twithHeaders,\n} from '@alxia/core';\nimport type { Session } from '@softistx/janus';\nimport { type DeviceCookieOptions, deviceOf } from './device';\nimport { authenticateOnce, type Found } from './lookup';\nimport { refused } from './refused';\nimport { sendSession, signOut } from './send';\nimport type { Auth, RequestAuth, UserOfAuth } from './types';\n\nexport interface SessionOptions<T extends string> {\n\t/** Only a user of this type is authenticated here; any other is anonymous. */\n\treadonly type?: T;\n\t/**\n\t * `true`: an anonymous request is answered 401 and the route never runs,\n\t * so `user` is never `null` in it — and the 401 is in its type. A\n\t * `boolean` known only at runtime keeps the 401 in the type, and `user`\n\t * may be `null`.\n\t */\n\treadonly required?: boolean;\n\t/** The device cookie `auth.device` reads and `auth.send` sets. */\n\treadonly device?: DeviceCookieOptions;\n}\n\n/** The body of the 401 a required session answers. */\nexport interface UnauthenticatedBody {\n\treadonly error: 'unauthenticated';\n}\n\n/** The 401 a required session answers under `alxia({ errors: 'problem' })`. */\nexport type UnauthenticatedProblem = Problem<401>;\n\ntype UserOf<A, T> = Extract<UserOfAuth<A>, { readonly type: T }>;\n\n/** What a session gives the routes after it. */\ntype Given<User, S> = {\n\treadonly user: User;\n\treadonly session: S;\n\treadonly auth: RequestAuth;\n};\n\n/** What `session()` makes: a middleware that gives `Added`, or answers `Refused`. */\nexport type SessionMiddleware<\n\tAdded extends object,\n\tRefused = never,\n> = Middleware<Empty, Promise<Refused | Next<Added>>>;\n\n/**\n * Who a request belongs to, as a middleware: `auth.authenticate(request)`,\n * and the routes declared after it read `user` and `session` — typed by\n * the user schema, narrowed by `type`. Given to `app.use`, it runs on a\n * request no route matches too: a required session answers it 401 before\n * its 404.\n *\n * **An outage is not anonymous**: a store that cannot answer throws\n * `STORE_FAILED`, which `janusErrors()` — given to `use` before it —\n * answers 503, never 401.\n *\n * A session renewed in passing is sent again as a cookie, after the route —\n * only to a request that presented it as one (a `Bearer` client is never\n * handed a cookie), and never over a session cookie the route set itself.\n *\n * ```ts\n * app.use(janusErrors(), session(accounts, { required: true }))\n * .get('/me', ({ user, reply }) => reply.ok(user));\n * ```\n */\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(\n\tauth: A,\n\toptions: SessionOptions<T> & { readonly required: true },\n): SessionMiddleware<\n\tGiven<UserOf<A, T>, Session>,\n\tReply<401, UnauthenticatedBody | UnauthenticatedProblem>\n>;\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(\n\tauth: A,\n\toptions?: SessionOptions<T> & { readonly required?: false },\n): SessionMiddleware<Given<UserOf<A, T> | null, Session | null>>;\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(\n\tauth: A,\n\toptions?: SessionOptions<T>,\n): SessionMiddleware<\n\tGiven<UserOf<A, T> | null, Session | null>,\n\tReply<401, UnauthenticatedBody | UnauthenticatedProblem>\n>;\nexport function session(\n\tauth: Auth<{ readonly type: string }>,\n\toptions: SessionOptions<string> = {},\n): unknown {\n\tconst unauthenticated: UnauthenticatedBody = { error: 'unauthenticated' };\n\treturn defineMiddleware(async function session(ctx, next) {\n\t\tconst { request } = ctx;\n\t\tconst found = await authenticateOnce(auth, request, options.type);\n\t\tif (found === null && options.required === true) {\n\t\t\treturn refused(ctx, 401, unauthenticated, 'The request has no session');\n\t\t}\n\t\tconst bound: RequestAuth = {\n\t\t\tdevice: deviceOf(ctx, options.device),\n\t\t\tsend: (signedIn) => sendSession(ctx, auth, signedIn, options),\n\t\t\tsignOut: () => signOut(ctx, auth),\n\t\t};\n\t\t// Settled: a refusal's 400, an error's answer, still carry the renewal.\n\t\tconst response = await settle(\n\t\t\tctx,\n\t\t\tnext({\n\t\t\t\tuser: found?.user ?? null,\n\t\t\t\tsession: found?.session ?? null,\n\t\t\t\tauth: bound,\n\t\t\t}),\n\t\t);\n\t\treturn found?.renewed === true\n\t\t\t? renewed(auth, request, found, response)\n\t\t\t: response;\n\t});\n}\n\n/**\n * A session renewed in passing, sent again as a cookie: only to a request\n * that presented it as one, and never over a cookie the route set itself —\n * a sign-in or a sign-out behind it must not be undone by the session it\n * replaced.\n */\nfunction renewed<R extends Response>(\n\tauth: Auth<{ readonly type: string }>,\n\trequest: Request,\n\tfound: NonNullable<Found>,\n\tresponse: R,\n): R {\n\tconst name = `${auth.cookie.name}=`;\n\tconst presented = new Bun.CookieMap(request.headers.get('cookie') ?? '').get(\n\t\tauth.cookie.name,\n\t);\n\tif (\n\t\tpresented !== found.token ||\n\t\tresponse.headers.getSetCookie().some((value) => value.startsWith(name))\n\t) {\n\t\treturn response;\n\t}\n\treturn withHeaders(response, (headers) =>\n\t\theaders.append(\n\t\t\t'set-cookie',\n\t\t\tauth.cookie.serialize(found.token, found.session),\n\t\t),\n\t) as R;\n}\n\nmarkFactory(session);\n",
11
+ "import type { Authenticated } from '@softistx/janus';\nimport type { Auth } from './types';\n\nexport type Found = Authenticated<{ readonly type: string }> | null;\n\n/**\n * Each request's lookups, by instance and by `type`: an open `session()`\n * and a required one behind it, of the same `type`, look the session up\n * once. They live as long as the `Request` object; a lookup that failed is\n * forgotten, so the next dispatch of that object asks again.\n */\nconst lookups = new WeakMap<\n\tobject,\n\tWeakMap<Request, Map<string, Promise<Found>>>\n>();\n\nexport function authenticateOnce(\n\tauth: Auth<{ readonly type: string }>,\n\trequest: Request,\n\ttype: string | undefined,\n): Promise<Found> {\n\tlet byRequest = lookups.get(auth);\n\tif (byRequest === undefined) {\n\t\tbyRequest = new WeakMap();\n\t\tlookups.set(auth, byRequest);\n\t}\n\tlet byType = byRequest.get(request);\n\tif (byType === undefined) {\n\t\tbyType = new Map();\n\t\tbyRequest.set(request, byType);\n\t}\n\tconst key = type ?? '';\n\tlet found = byType.get(key);\n\tif (found === undefined) {\n\t\tfound = auth.authenticate(\n\t\t\trequest,\n\t\t\ttype === undefined ? undefined : { type },\n\t\t);\n\t\tbyType.set(key, found);\n\t\tconst asked = found;\n\t\tasked.catch(() => {\n\t\t\tif (byType.get(key) === asked) byType.delete(key);\n\t\t});\n\t}\n\treturn found;\n}\n"
12
12
  ],
13
13
  "mappings": "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",
14
- "debugId": "CC7FB948F0D1EAE664756E2164756E21",
14
+ "debugId": "6AAB6689598F45D564756E2164756E21",
15
15
  "names": []
16
16
  }
package/dist/lookup.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Authenticated } from '@nxgt/janus';
1
+ import type { Authenticated } from '@softistx/janus';
2
2
  import type { Auth } from './types';
3
3
  export type Found = Authenticated<{
4
4
  readonly type: string;
@@ -1 +1 @@
1
- {"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../src/lookup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,MAAM,KAAK,GAAG,aAAa,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAAC;AAapE,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,IAAI,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACrC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,GAAG,SAAS,GACtB,OAAO,CAAC,KAAK,CAAC,CAyBhB"}
1
+ {"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../src/lookup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,MAAM,KAAK,GAAG,aAAa,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAAC;AAapE,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,IAAI,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACrC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,GAAG,SAAS,GACtB,OAAO,CAAC,KAAK,CAAC,CAyBhB"}
@@ -1,5 +1,5 @@
1
1
  import type { BaseContext } from '@alxia/core';
2
- import type { CheckableOf, CtxOf, FieldsOf, ModelConfig, ObjectTypeOf, SubjectRef } from '@nxgt/janus/permissions';
2
+ import type { CheckableOf, CtxOf, FieldsOf, ModelConfig, ObjectTypeOf, SubjectRef } from '@softistx/janus/permissions';
3
3
  /**
4
4
  * An object of type `T` as the application loads it: its id, every field a
5
5
  * `fromField` of its type reads, and whatever else it carries.
@@ -12,7 +12,7 @@ export type ObjectData<C extends ModelConfig, T extends ObjectTypeOf<C>> = {
12
12
  export type Awaitable<V> = V | Promise<V>;
13
13
  /**
14
14
  * The options of `permission()`: `ctx` required exactly when a condition of
15
- * the permission is reachable, as for `can()`. `@nxgt/janus-hono`'s, over
15
+ * the permission is reachable, as for `can()`. `@softistx/janus-hono`'s, over
16
16
  * alxia's context, kept twice on purpose — except that `SubjectCtx` and
17
17
  * `CheckCtx`, what `subject` and `ctx` read beyond `BaseContext`, are
18
18
  * inferred from their annotated parameters, which the Hono ones are not.
@@ -1 +1 @@
1
- {"version":3,"file":"permission-options.d.ts","sourceRoot":"","sources":["../src/permission-options.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permission-options.d.ts","sourceRoot":"","sources":["../src/permission-options.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { type BaseContext, type Problem, type RequiresOf } from '@alxia/core';
2
- import type { CheckableOf, ModelConfig, ObjectTypeOf, Permissions } from '@nxgt/janus/permissions';
2
+ import type { CheckableOf, ModelConfig, ObjectTypeOf, Permissions } from '@softistx/janus/permissions';
3
3
  import type { Awaitable, ObjectData, OptionsArgs } from './permission-options';
4
4
  /** The body of each refusal. */
5
5
  export interface PermissionRefusedBody {
@@ -1 +1 @@
1
- {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,WAAW,EAGhB,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EAEX,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAG/E,gCAAgC;AAChC,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,WAAW,GAAG,WAAW,CAAC;CAC9D;AAED,wFAAwF;AACxF,MAAM,MAAM,wBAAwB,GAAG,OAAO,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;AAShE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CACzB,CAAC,SAAS,WAAW,EACrB,KAAK,CAAC,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,EAC/B,KAAK,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACjC,CAAC,SAAS,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,OAAO,SAAS,MAAM,GAAG,WAAW,EACpC,UAAU,SAAS,MAAM,GAAG,WAAW,EACvC,QAAQ,SAAS,MAAM,GAAG,WAAW,EAErC,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EACnC,UAAU,EAAE,CAAC,EACb,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,EACzD,GAAG,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC;;kCA+CzD;AAED;;;;;GAKG;AACH,KAAK,YAAY,CAAC,OAAO,EAAE,UAAU,IAAI,CAAC,OAAO,CAAC,SAAS;IAC1D;QAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;KAAE;CACtB,GACE,MAAM,GACN,CAAC,UAAU,CAAC,SAAS,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,GAC5C,SAAS,GACT,KAAK,CAAC;AAEV;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,GACvC,CAAC,GAAG,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAK3C"}
1
+ {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,WAAW,EAGhB,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EAEX,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAG/E,gCAAgC;AAChC,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,WAAW,GAAG,WAAW,CAAC;CAC9D;AAED,wFAAwF;AACxF,MAAM,MAAM,wBAAwB,GAAG,OAAO,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;AAShE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CACzB,CAAC,SAAS,WAAW,EACrB,KAAK,CAAC,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,EAC/B,KAAK,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACjC,CAAC,SAAS,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,OAAO,SAAS,MAAM,GAAG,WAAW,EACpC,UAAU,SAAS,MAAM,GAAG,WAAW,EACvC,QAAQ,SAAS,MAAM,GAAG,WAAW,EAErC,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EACnC,UAAU,EAAE,CAAC,EACb,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,EACzD,GAAG,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC;;kCA+CzD;AAED;;;;;GAKG;AACH,KAAK,YAAY,CAAC,OAAO,EAAE,UAAU,IAAI,CAAC,OAAO,CAAC,SAAS;IAC1D;QAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;KAAE;CACtB,GACE,MAAM,GACN,CAAC,UAAU,CAAC,SAAS,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,GAC5C,SAAS,GACT,KAAK,CAAC;AAEV;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,GACvC,CAAC,GAAG,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAK3C"}
package/dist/session.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Empty, type Middleware, type Next, type Problem, type Reply } from '@alxia/core';
2
- import type { Session } from '@nxgt/janus';
2
+ import type { Session } from '@softistx/janus';
3
3
  import { type DeviceCookieOptions } from './device';
4
4
  import type { Auth, RequestAuth, UserOfAuth } from './types';
5
5
  export interface SessionOptions<T extends string> {
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { SharedApi, SignedIn } from '@nxgt/janus';
1
+ import type { SharedApi, SignedIn } from '@softistx/janus';
2
2
  /** Anything `janus()` answered: the part of it this package calls. */
3
3
  export type Auth<U extends {
4
4
  readonly type: string;
@@ -6,7 +6,7 @@ export type Auth<U extends {
6
6
  /** The users an `auth` instance knows, as a union narrowed by `user.type`. */
7
7
  export type UserOfAuth<A> = A extends Auth<infer U> ? U : never;
8
8
  /**
9
- * What `sendSession` takes: the part of `@nxgt/janus`'s `SignedIn` — or of
9
+ * What `sendSession` takes: the part of `@softistx/janus`'s `SignedIn` — or of
10
10
  * `signUp`'s answer — that opens a session.
11
11
  */
12
12
  export interface SessionOpened<U> extends Pick<SignedIn<U>, 'token' | 'session' | 'user'> {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvD,sEAAsE;AACtE,MAAM,MAAM,IAAI,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,IAAI,IAAI,CAC3D,SAAS,CAAC,CAAC,CAAC,EACZ,cAAc,GAAG,SAAS,GAAG,QAAQ,CACrC,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,CAC/B,SAAQ,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC3B,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;OAGG;IACH,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACvC,6EAA6E;IAC7E,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3D,sEAAsE;AACtE,MAAM,MAAM,IAAI,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,IAAI,IAAI,CAC3D,SAAS,CAAC,CAAC,CAAC,EACZ,cAAc,GAAG,SAAS,GAAG,QAAQ,CACrC,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,CAC/B,SAAQ,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC3B,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;OAGG;IACH,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACvC,6EAA6E;IAC7E,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5B"}
@@ -1,14 +1,14 @@
1
1
  # Errors
2
2
 
3
3
  This page covers `janusErrors()`: the middleware that answers every refusal
4
- `@nxgt/janus` throws — a wrong password, a login taken, a store down —
4
+ `@softistx/janus` throws — a wrong password, a login taken, a store down —
5
5
  with its status and a body a client can act on, typed on the routes after
6
6
  it; and `bodyOf` and `statusOf`, the two functions it is made of.
7
7
 
8
8
  ```ts
9
9
  import { alxia, validate } from '@alxia/core';
10
10
  import { janusErrors, session } from '@alxia/janus';
11
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
11
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
12
12
  import { z } from 'zod';
13
13
 
14
14
  const accounts = janus({
@@ -76,7 +76,7 @@ const app = alxia()
76
76
 
77
77
  ## Statuses
78
78
 
79
- `statusOf(code)` is `@nxgt/janus`'s, re-exported:
79
+ `statusOf(code)` is `@softistx/janus`'s, re-exported:
80
80
 
81
81
  | Status | Codes |
82
82
  | --- | --- |
@@ -85,7 +85,7 @@ const app = alxia()
85
85
  | 403 | `USER_INACTIVE`, `STEP_UP_REQUIRED` |
86
86
  | 404 | `NOT_FOUND` |
87
87
  | 409 | `LOGIN_TAKEN`, `VERSION_CONFLICT`, `SECOND_FACTOR_NOT_ENROLLED`, `SECOND_FACTOR_ACTIVE` |
88
- | 429 | `MAIL_THROTTLED` — too many e-mails asked for one address or user (`@nxgt/janus` 0.18 or later) |
88
+ | 429 | `MAIL_THROTTLED` — too many e-mails asked for one address or user (`@softistx/janus` 0.18 or later) |
89
89
  | 500 | `PERMISSION_DEPTH` — a permission check walked past `maxDepth`: not a denial |
90
90
  | 501 | `UNSUPPORTED` — a wiring mistake: a store lacks a method a flow needs |
91
91
  | 503 | `STORE_FAILED` — **an outage, never a 401 or a 404** |
@@ -109,12 +109,12 @@ your logs, and stay on the `JanusError`.
109
109
  | `PASSWORD_TOO_SHORT` | `{ code, minLength? }` |
110
110
  | `CODE_INVALID` | `{ code, attemptsLeft? }` |
111
111
  | `CREDENTIALS_INVALID` | `{ code, retryAfter? }` — seconds; also sent as `Retry-After` |
112
- | `MAIL_THROTTLED` | `{ code, retryAfter? }` — seconds; also sent as `Retry-After` (needs `@nxgt/janus` 0.18 or later, which adds the code) |
112
+ | `MAIL_THROTTLED` | `{ code, retryAfter? }` — seconds; also sent as `Retry-After` (needs `@softistx/janus` 0.18 or later, which adds the code) |
113
113
  | every other code | `{ code }` |
114
114
 
115
115
  ```ts
116
116
  import { bodyOf } from '@alxia/janus';
117
- import { NotFoundError } from '@nxgt/janus';
117
+ import { NotFoundError } from '@softistx/janus';
118
118
 
119
119
  bodyOf(new NotFoundError('user')); // { code: 'NOT_FOUND' }
120
120
  ```
@@ -223,10 +223,10 @@ interface JanusErrorBody {
223
223
  readonly retryAfter?: number;
224
224
  }
225
225
 
226
- function statusOf(code: JanusErrorCode): JanusErrorStatus; // @nxgt/janus's
226
+ function statusOf(code: JanusErrorCode): JanusErrorStatus; // @softistx/janus's
227
227
  ```
228
228
 
229
- `JanusError`, `JanusErrorCode` and `JanusErrorStatus` are `@nxgt/janus`'s;
229
+ `JanusError`, `JanusErrorCode` and `JanusErrorStatus` are `@softistx/janus`'s;
230
230
  `Middleware`, `Next`, `Reply`, `Problem` and `BaseContext` are `@alxia/core`'s.
231
231
 
232
232
  The permission guard's own refusals — `{ error: 'forbidden' }` and the
@@ -1,14 +1,14 @@
1
1
  # Permissions
2
2
 
3
- This page covers `permission()`: a guard on `@nxgt/janus/permissions` that
3
+ This page covers `permission()`: a guard on `@softistx/janus/permissions` that
4
4
  lets the routes after it run only when the subject holds a permission on
5
5
  the object they are about, loads that object once, and hands it to them.
6
6
 
7
7
  ```ts
8
8
  import { alxia } from '@alxia/core';
9
9
  import { byParam, janusErrors, permission, session } from '@alxia/janus';
10
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
11
- import { createMemoryRelations, defineModel, permissions } from '@nxgt/janus/permissions';
10
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
11
+ import { createMemoryRelations, defineModel, permissions } from '@softistx/janus/permissions';
12
12
  import { z } from 'zod';
13
13
 
14
14
  const relations = createMemoryRelations();
@@ -51,7 +51,7 @@ should get. `janusErrors()` is given to `use` before `session()`, so it
51
51
  answers what `session()` and the guard throw.
52
52
 
53
53
  `object` is what `findRecord` answered, typed as such. Granting access is
54
- `@nxgt/janus`'s:
54
+ `@softistx/janus`'s:
55
55
 
56
56
  ```ts
57
57
  await access.grant({ type: 'record', id: 'r1' }, 'owners', user); // user: what janus() answered
@@ -142,7 +142,7 @@ reads**, as a `string` or `null` — `ObjectData` says so, and a `load`
142
142
  answering an object without one does not compile:
143
143
 
144
144
  ```ts
145
- import { createMemoryRelations, defineModel, fromField, permissions } from '@nxgt/janus/permissions';
145
+ import { createMemoryRelations, defineModel, fromField, permissions } from '@softistx/janus/permissions';
146
146
 
147
147
  const access = permissions({
148
148
  model: defineModel({
@@ -172,7 +172,7 @@ A permission whose rule is a `when(…)` condition needs a context to decide.
172
172
  require it for that permission, and refuse it for one without a condition:
173
173
 
174
174
  ```ts
175
- import { when } from '@nxgt/janus/permissions';
175
+ import { when } from '@softistx/janus/permissions';
176
176
 
177
177
  const model = defineModel({
178
178
  subjects: accounts.types,
@@ -371,5 +371,5 @@ two parameters default to `BaseContext`, so `OptionsArgs<C, T, P, O>` is
371
371
  the unannotated one.
372
372
 
373
373
  `ModelConfig`, `ObjectTypeOf`, `CheckableOf`, `CtxOf`, `FieldsOf`,
374
- `SubjectRef` and `Permissions` are `@nxgt/janus/permissions`'s; its
374
+ `SubjectRef` and `Permissions` are `@softistx/janus/permissions`'s; its
375
375
  permissions guide covers the model itself.
@@ -8,7 +8,7 @@ session's cookie again.
8
8
  ```ts
9
9
  import { alxia } from '@alxia/core';
10
10
  import { janusErrors, session } from '@alxia/janus';
11
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
11
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
12
12
  import { z } from 'zod';
13
13
 
14
14
  const accounts = janus({
@@ -105,7 +105,7 @@ another type is anonymous there. Scope it with `group`:
105
105
  ```ts
106
106
  import { alxia } from '@alxia/core';
107
107
  import { janusErrors, session } from '@alxia/janus';
108
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
108
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
109
109
  import { z } from 'zod';
110
110
 
111
111
  const accounts = janus({
@@ -148,7 +148,7 @@ A patient's session on `/staff/me` is a 401, as if they had sent none. A
148
148
  | `auth` | `RequestAuth`: `send`, `signOut`, `device` | the same | the same |
149
149
  | a 401 to an anonymous request | no | yes | yes |
150
150
 
151
- `Session` is `@nxgt/janus`'s: `id`, `userId`, `authenticatedAt`,
151
+ `Session` is `@softistx/janus`'s: `id`, `userId`, `authenticatedAt`,
152
152
  `expiresAt`, `revokedAt`, `createdAt`. The token is never in it.
153
153
 
154
154
  `user` is the whole user as `janus()` answers it — your schema's fields,
@@ -173,7 +173,7 @@ twice — once open, for the sign-in routes; once required, for the rest:
173
173
  ```ts
174
174
  import { alxia, validate } from '@alxia/core';
175
175
  import { janusErrors, session } from '@alxia/janus';
176
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
176
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
177
177
  import { z } from 'zod';
178
178
 
179
179
  const accounts = janus({
@@ -231,7 +231,7 @@ conditions:
231
231
  ```ts
232
232
  import { alxia } from '@alxia/core';
233
233
  import { session } from '@alxia/janus';
234
- import { createMemoryStores, fixedClock, janus, scryptHasher } from '@nxgt/janus';
234
+ import { createMemoryStores, fixedClock, janus, scryptHasher } from '@softistx/janus';
235
235
  import { z } from 'zod';
236
236
 
237
237
  const clock = fixedClock(Date.UTC(2026, 8, 24));
@@ -286,7 +286,7 @@ given to every request.
286
286
  import { expect, test } from 'bun:test';
287
287
  import { alxia } from '@alxia/core';
288
288
  import { session } from '@alxia/janus';
289
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
289
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
290
290
  import { z } from 'zod';
291
291
 
292
292
  const accounts = janus({
@@ -361,7 +361,7 @@ type UserOfAuth<A> = A extends Auth<infer U> ? U : never;
361
361
  [Signing in and out](sign-in-and-out.md#signatures).
362
362
  `UserOf<A, T>` is `Extract<UserOfAuth<A>, { readonly type: T }>`; it is not
363
363
  exported. `Middleware`, `Next`, `Empty`, `Reply` and `Problem` are `@alxia/core`'s; `Session` and
364
- `SharedApi` are `@nxgt/janus`'s.
364
+ `SharedApi` are `@softistx/janus`'s.
365
365
 
366
366
  Next: [Signing in and out](sign-in-and-out.md) sets the cookie this middleware
367
367
  reads.
@@ -8,7 +8,7 @@ before — and the unbound functions for code outside a route.
8
8
  ```ts
9
9
  import { alxia, validate } from '@alxia/core';
10
10
  import { janusErrors, session } from '@alxia/janus';
11
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
11
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
12
12
  import { z } from 'zod';
13
13
 
14
14
  const accounts = janus({
@@ -76,7 +76,7 @@ same:
76
76
  ```ts
77
77
  import { alxia, validate } from '@alxia/core';
78
78
  import { janusErrors, session } from '@alxia/janus';
79
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
79
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
80
80
  import { z } from 'zod';
81
81
 
82
82
  const accounts = janus({
@@ -116,7 +116,7 @@ Switch on `status` first:
116
116
  })
117
117
  ```
118
118
 
119
- The challenge is a secret like a session token; `@nxgt/janus`'s
119
+ The challenge is a secret like a session token; `@softistx/janus`'s
120
120
  second-factor guide says where to keep it and how to confirm it.
121
121
 
122
122
  ## `ctx.auth.signOut()`
@@ -138,7 +138,7 @@ await again.json(); // false, and the cookie is cleared all the same
138
138
 
139
139
  Behind `session(accounts)`, an anonymous request gets that `false`; behind
140
140
  `session(accounts, { required: true })`, it gets a 401 instead. Signing out of
141
- every device is `accounts.signOutEverywhere(user)`, `@nxgt/janus`'s.
141
+ every device is `accounts.signOutEverywhere(user)`, `@softistx/janus`'s.
142
142
 
143
143
  A store that cannot answer makes it throw `STORE_FAILED`, a 503 through
144
144
  [`janusErrors()`](errors.md); the cookie is not cleared then.
@@ -153,7 +153,7 @@ client keeps in a long-lived cookie and presents at its next sign-in.
153
153
  ```ts
154
154
  import { alxia, validate } from '@alxia/core';
155
155
  import { janusErrors, session } from '@alxia/janus';
156
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
156
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
157
157
  import { z } from 'zod';
158
158
 
159
159
  const accounts = janus({
@@ -224,7 +224,7 @@ Given to `session(accounts, { device })`, to `sendSession` as
224
224
  | `maxAge` | `number`, seconds | `34_560_000` (400 days, the most a browser keeps) | the cookie's `Max-Age` |
225
225
 
226
226
  The cookie is always `HttpOnly`. `DEVICE_COOKIE` is `'janus-device'`, the
227
- name `@nxgt/janus-hono` uses, so a device one of them remembers, the other
227
+ name `@softistx/janus-hono` uses, so a device one of them remembers, the other
228
228
  does too:
229
229
 
230
230
  ```ts
@@ -243,7 +243,7 @@ any `session()`, which then looks up no session at all:
243
243
  ```ts
244
244
  import { alxia, validate } from '@alxia/core';
245
245
  import { deviceOf, janusErrors, sendSession, session, signOut } from '@alxia/janus';
246
- import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
246
+ import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
247
247
  import { z } from 'zod';
248
248
 
249
249
  const accounts = janus({
@@ -303,7 +303,7 @@ interface RequestAuth {
303
303
  signOut(): Promise<boolean>;
304
304
  }
305
305
 
306
- /** The part of @nxgt/janus's SignedIn — or of signUp's answer — that opens a session. */
306
+ /** The part of @softistx/janus's SignedIn — or of signUp's answer — that opens a session. */
307
307
  interface SessionOpened<U> extends Pick<SignedIn<U>, 'token' | 'session' | 'user'> {
308
308
  readonly deviceToken?: string | null;
309
309
  }
@@ -336,5 +336,5 @@ interface DeviceCookieOptions {
336
336
  const DEVICE_COOKIE = 'janus-device';
337
337
  ```
338
338
 
339
- `ResponseSettings` is `@alxia/core`'s, `Session` is `@nxgt/janus`'s;
339
+ `ResponseSettings` is `@alxia/core`'s, `Session` is `@softistx/janus`'s;
340
340
  `Auth` is described in [Sessions](sessions.md#signature).
package/docs/roadmap.md CHANGED
@@ -20,12 +20,12 @@ Nothing scheduled yet.
20
20
  ## Not planned
21
21
 
22
22
  - **A runtime dependency.** `@alxia/janus` declares no dependency, only
23
- peers: it is built on `@alxia/core`'s public API, `@nxgt/janus`, and
23
+ peers: it is built on `@alxia/core`'s public API, `@softistx/janus`, and
24
24
  Bun's own cookie parser.
25
25
  - **Identities of its own.** Users, passwords, sessions and permissions are
26
- `@nxgt/janus`'s, with your database behind its stores: this package only
26
+ `@softistx/janus`'s, with your database behind its stores: this package only
27
27
  wires them into an app — the context, the cookies, the answers — so a
28
- flow `@nxgt/janus` adds is usable here without a release of this one.
28
+ flow `@softistx/janus` adds is usable here without a release of this one.
29
29
  - **A BFF that calls a janus API.** An app that renders its pages and holds
30
30
  the session in its own cookie does not run this package: it calls the API.
31
31
  Its session is `@alxia/react-router/session`.
@@ -63,14 +63,14 @@ Nothing scheduled yet.
63
63
  a session renewed in passing is sent again, only to a client that
64
64
  presented it as a cookie, and never over one the route set itself.
65
65
  - **Devices remembered.** `ctx.auth.device` and `ctx.auth.send` read and
66
- write the device cookie under the name `@nxgt/janus-hono` uses, so a
66
+ write the device cookie under the name `@softistx/janus-hono` uses, so a
67
67
  device one remembers the other does too.
68
68
  - **Janus's refusals answered.** `janusErrors()` answers every `JanusError`
69
69
  with its status and a body holding only what a client can act on, typed
70
70
  on the routes after it, with `Retry-After` for a throttled sign-in; an
71
71
  outage is a 503, never a 401.
72
72
  - **A permission guard.** `permission(access, permission, type, load)`
73
- loads the object once, checks it with `@nxgt/janus/permissions`, and
73
+ loads the object once, checks it with `@softistx/janus/permissions`, and
74
74
  hands it to the routes as `object`; anonymous, not found and denied are
75
75
  a typed 401, 404 and 403, and a condition's context is required by the
76
76
  types exactly when the permission has one.
@@ -46,6 +46,20 @@ symptom, under [Traps](#traps).
46
46
  - [Every guarded route answers 404](#every-guarded-route-answers-404)
47
47
  - [Routes that have nothing to do with the object answer 401 or 404](#routes-that-have-nothing-to-do-with-the-object-answer-401-or-404)
48
48
 
49
+ ## Install
50
+
51
+ ### `404 Not Found` or `401 Unauthorized` on `@softistx/janus`
52
+
53
+ **Why:** `@softistx/janus` is private, on GitHub Packages; the install has no registry or token for the `@softistx` scope.
54
+
55
+ ```toml
56
+ # bunfig.toml
57
+ [install.scopes]
58
+ "@softistx" = { url = "https://npm.pkg.github.com/", token = "$GITHUB_TOKEN" }
59
+ ```
60
+
61
+ Export `GITHUB_TOKEN` with `read:packages` before `bun install`.
62
+
49
63
  ## Types: sessions and cookies
50
64
 
51
65
  ### `Property 'user' does not exist on type 'Context<…>'`
@@ -552,7 +566,7 @@ passwords tried at one login in fifteen minutes, `signIn` refuses until the
552
566
  next window, the right password included. Nothing is locked.
553
567
 
554
568
  **Fix:** wait `retryAfter` seconds, and show it to the user. The throttle is
555
- `janus({ signIn: { throttle } })`'s, in `@nxgt/janus`.
569
+ `janus({ signIn: { throttle } })`'s, in `@softistx/janus`.
556
570
 
557
571
  ### `429 {"code":"MAIL_THROTTLED","retryAfter":600}`
558
572
 
@@ -561,7 +575,7 @@ link, a sign-in code, a password reset, an address verification, a
561
575
  change-of-address e-mail (`changeEmail.send`) or a step-up code. The
562
576
  response carries `Retry-After: 600`.
563
577
 
564
- **Why:** `@nxgt/janus` 0.18 and later throttles the requests that send
578
+ **Why:** `@softistx/janus` 0.18 and later throttles the requests that send
565
579
  e-mail, on by default: past five in ten minutes for one address or user, it
566
580
  refuses until the next window. The count is per flow, per address or user,
567
581
  per window: five magic links and five reset e-mails count separately. The
@@ -569,8 +583,8 @@ last e-mail sent still works.
569
583
 
570
584
  **Fix:** wait `retryAfter` seconds, which `Retry-After` carries too, and tell
571
585
  the visitor to use the last e-mail they received or to wait. To tune it, pass
572
- `janus({ mail: { throttle: { attempts, window } } })`, in `@nxgt/janus`;
573
- `mail: { throttle: false }` turns it off. Needs `@nxgt/janus` 0.18 or later.
586
+ `janus({ mail: { throttle: { attempts, window } } })`, in `@softistx/janus`;
587
+ `mail: { throttle: false }` turns it off. Needs `@softistx/janus` 0.18 or later.
574
588
 
575
589
  ## Traps
576
590
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alxia/janus",
3
- "version": "0.3.18",
4
- "description": "Identities and sessions for alxia on @nxgt/janus: the user typed in the context, the session cookie kept and renewed, a typed 401",
3
+ "version": "0.4.0",
4
+ "description": "Identities and sessions for alxia on @softistx/janus: the user typed in the context, the session cookie kept and renewed, a typed 401",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -45,13 +45,13 @@
45
45
  "@alxia/i18n": "^0.4.7",
46
46
  "@alxia/language": "^0.3.15",
47
47
  "@nxgt/i18n": "^3.1.0",
48
- "@nxgt/janus": "^0.29.0",
48
+ "@softistx/janus": "^0.38.1",
49
49
  "@types/bun": "^1.4.4",
50
50
  "zod": "^4.6.5"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@alxia/core": "^0.19.0",
54
- "@nxgt/janus": ">=0.17.1 <0.30.0",
54
+ "@softistx/janus": ">=0.38.1 <0.39.0",
55
55
  "typescript": "^6.0.3 || ^7.0.0"
56
56
  }
57
57
  }