@nxgt/janus-hono 0.6.0 → 0.7.1

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
@@ -54,9 +54,9 @@ declarations import without extensions, so `nodenext` is not supported.
54
54
  | `SendSessionOptions` | `{ device? }`, the options of `sendSession` — for a wrapper of your own |
55
55
  | `fresh(maxAge, { clock? })` | Middleware, after `session()`. The route runs only for a session that proved who it is less than `maxAge` ago — signed in, or confirmed since by `auth.stepUp.confirm`. An older one throws `STEP_UP_REQUIRED`, which `janusErrors()` answers 403; an anonymous request is 401 with no body. `clock` is the one given to `janus()`, in a spec |
56
56
  | `signOut(c, auth)` | Revokes the session the request presents and clears the cookie, whatever the answer. `false` when the request presented no session, or an unknown one |
57
- | `janusErrors({ report?, fallback? })` | An `app.onError` handler: every `JanusError` answered with `statusOf(code)` and `bodyOf(error)` — and a `Retry-After` header when it carries `retryAfter`, a throttled sign-in; anything else to `fallback`, or to Hono's own handling. `report(error, c)` sees every one answered 5xx first — `STORE_FAILED` and the like, for your logs — and cannot change the answer: one that throws or rejects is a warning, and the 503 is sent |
58
- | `statusOf(code)` | The status a code deserves — `@nxgt/janus`'s `statusOf`, typed as Hono's `ContentfulStatusCode`: `STORE_FAILED` 503, `CREDENTIALS_INVALID` and `CODE_INVALID` 401, `USER_INACTIVE` and `STEP_UP_REQUIRED` 403, `LOGIN_TAKEN`, `SECOND_FACTOR_NOT_ENROLLED` and `SECOND_FACTOR_ACTIVE` 409, … Exhaustive over `JanusErrorCode` |
59
- | `bodyOf(error)` | `{ code }`, plus `issues` for `USER_INVALID`, `minLength` for `PASSWORD_TOO_SHORT` and `attemptsLeft` for any `CODE_INVALID` that carries it — `secondFactor.confirm`'s, `secondFactor.recover`'s, `signInCode.confirm`'s and `stepUp.confirm`'s — and `retryAfter` for a `CREDENTIALS_INVALID` whose login is throttled: what the client can act on, and nothing else |
57
+ | `janusErrors({ report?, fallback? })` | An `app.onError` handler: every `JanusError` answered with `statusOf(code)` and `bodyOf(error)` — and a `Retry-After` header when it carries `retryAfter`, a throttled sign-in or a throttled e-mail request; anything else to `fallback`, or to Hono's own handling. `report(error, c)` sees every one answered 5xx first — `STORE_FAILED` and the like, for your logs — and cannot change the answer: one that throws or rejects is a warning, and the 503 is sent |
58
+ | `statusOf(code)` | The status a code deserves — `@nxgt/janus`'s `statusOf`, typed as Hono's `ContentfulStatusCode`: `STORE_FAILED` 503, `CREDENTIALS_INVALID` and `CODE_INVALID` 401, `MAIL_THROTTLED` 429, `USER_INACTIVE` and `STEP_UP_REQUIRED` 403, `LOGIN_TAKEN`, `SECOND_FACTOR_NOT_ENROLLED` and `SECOND_FACTOR_ACTIVE` 409, … Exhaustive over `JanusErrorCode` |
59
+ | `bodyOf(error)` | `{ code }`, plus `issues` for `USER_INVALID`, `minLength` for `PASSWORD_TOO_SHORT` and `attemptsLeft` for any `CODE_INVALID` that carries it — `secondFactor.confirm`'s, `secondFactor.recover`'s, `signInCode.confirm`'s and `stepUp.confirm`'s — and `retryAfter` for a `CREDENTIALS_INVALID` whose login is throttled, and for `MAIL_THROTTLED` (429): what the client can act on, and nothing else |
60
60
  | `SessionOptions<Type>` | `{ type?, required? }`, the options of `session()` — for a wrapper of your own |
61
61
  | `SessionEnv<typeof auth, Type?, Required?>` | The `Env` `session()` sets, for `new Hono<SessionEnv<typeof auth>>()` |
62
62
  | `UserOfAuth<typeof auth>` | The users an instance knows, as a union narrowed by `user.type` |
package/dist/errors.d.ts CHANGED
@@ -13,7 +13,8 @@ export declare function statusOf(code: JanusErrorCode): ContentfulStatusCode;
13
13
  /**
14
14
  * The body a refusal is answered with: its `code`, and only what the client
15
15
  * can act on — the fields that failed the schema, the policy's minimum, the
16
- * attempts a challenge has left, the seconds a throttled sign-in waits.
16
+ * attempts a challenge has left, the seconds a throttled sign-in or a
17
+ * throttled request for an e-mail waits.
17
18
  * Never `reason`, `login`, a hash prefix or a cause: those are for your logs.
18
19
  */
19
20
  export declare function bodyOf(error: JanusError): {
@@ -40,8 +41,9 @@ export interface JanusErrorsOptions {
40
41
  }
41
42
  /**
42
43
  * An `app.onError` handler: every `JanusError` answered with its status and
43
- * `bodyOf(error)` — and a throttled sign-in with a `Retry-After` header, its
44
- * `retryAfter` in seconds; anything else handed to `fallback` — Hono's own
44
+ * `bodyOf(error)` — and a throttled sign-in, or a request past its e-mails
45
+ * (`MAIL_THROTTLED`, 429), with a `Retry-After` header, its `retryAfter` in
46
+ * seconds; anything else handed to `fallback` — Hono's own
45
47
  * behaviour when absent.
46
48
  *
47
49
  * Works for either side of `@nxgt/janus`: `can()`'s `STORE_FAILED` is a 503
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,KAAK,cAAc,EAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAElD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,oBAAoB,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG;IAC1C,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,CAmBA;AAgBD,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAC7D,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;CACjC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,YAAY,CAY1E"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,KAAK,cAAc,EAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAElD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,oBAAoB,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG;IAC1C,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,CAoBA;AAgBD,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAC7D,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;CACjC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,YAAY,CAY1E"}
@@ -1 +1 @@
1
- {"version":3,"file":"fresh.d.ts","sourceRoot":"","sources":["../src/fresh.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,KAAK,EACV,KAAK,QAAQ,EAEb,KAAK,OAAO,EAGZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,KAAK,CACpB,MAAM,EAAE,QAAQ,EAChB,OAAO,GAAE,YAAiB,GACxB,iBAAiB,CAAC;IAEpB,SAAS,EAAE;QAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;CACvC,CAAC,CAoBD"}
1
+ {"version":3,"file":"fresh.d.ts","sourceRoot":"","sources":["../src/fresh.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,KAAK,EACV,KAAK,QAAQ,EAEb,KAAK,OAAO,EAGZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,KAAK,CACpB,MAAM,EAAE,QAAQ,EAChB,OAAO,GAAE,YAAiB,GACxB,iBAAiB,CAAC;IAEpB,SAAS,EAAE;QAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;CACvC,CAAC,CAqBD"}
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ function permission(access, permission, type, load, options = {}) {
16
16
  return c.body(null, 403);
17
17
  c.set("object", object);
18
18
  await next();
19
+ return;
19
20
  };
20
21
  }
21
22
  function byParam(name, find) {
@@ -85,6 +86,7 @@ function session(auth, options = {}) {
85
86
  if (current?.renewed === true && getCookie2(c, auth.cookie.name) === current.token && !c.res.headers.getSetCookie().some((value) => value.startsWith(name))) {
86
87
  sendSession(c, auth, current);
87
88
  }
89
+ return;
88
90
  };
89
91
  }
90
92
  function sendSession(c, auth, signedIn, options = {}) {
@@ -109,13 +111,13 @@ function bindJanus(instances) {
109
111
  provide: () => provide(instances)
110
112
  };
111
113
  if (auth !== undefined) {
112
- bound.session = (options = {}) => session(auth, options);
113
- bound.sendSession = (c, signedIn, options) => sendSession(c, auth, signedIn, options);
114
- bound.signOut = (c) => signOut(c, auth);
114
+ bound["session"] = (options = {}) => session(auth, options);
115
+ bound["sendSession"] = (c, signedIn, options) => sendSession(c, auth, signedIn, options);
116
+ bound["signOut"] = (c) => signOut(c, auth);
115
117
  }
116
118
  if (access !== undefined) {
117
119
  const guard = permission;
118
- bound.permission = (...rest) => guard(access, ...rest);
120
+ bound["permission"] = (...rest) => guard(access, ...rest);
119
121
  }
120
122
  return bound;
121
123
  }
@@ -136,6 +138,7 @@ function bodyOf(error) {
136
138
  case "CODE_INVALID":
137
139
  return error.attemptsLeft === undefined ? { code: error.code } : { code: error.code, attemptsLeft: error.attemptsLeft };
138
140
  case "CREDENTIALS_INVALID":
141
+ case "MAIL_THROTTLED":
139
142
  return error.retryAfter === undefined ? { code: error.code } : { code: error.code, retryAfter: error.retryAfter };
140
143
  default:
141
144
  return { code: error.code };
@@ -189,6 +192,7 @@ function fresh(maxAge, options = {}) {
189
192
  throw new StepUpRequiredError("fresh(): the session proved who it is longer ago than maxAge — confirm with a step-up", { userId: current.userId });
190
193
  }
191
194
  await next();
195
+ return;
192
196
  };
193
197
  }
194
198
  export {
@@ -207,5 +211,5 @@ export {
207
211
  statusOf
208
212
  };
209
213
 
210
- //# debugId=21C4E2121FB06E7D64756E2164756E21
214
+ //# debugId=3BB1888C6A6D634864756E2164756E21
211
215
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -2,15 +2,15 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/permission.ts", "../src/provide.ts", "../src/session.ts", "../src/device.ts", "../src/bind.ts", "../src/errors.ts", "../src/fresh.ts"],
4
4
  "sourcesContent": [
5
- "import type {\n\tCheckableOf,\n\tCtxOf,\n\tFieldsOf,\n\tModelConfig,\n\tObjectTypeOf,\n\tPermissions,\n\tSubjectRef,\n} from '@nxgt/janus/permissions';\nimport type { Context, MiddlewareHandler } from 'hono';\n\n/**\n * An object of type `T` as the application loads it: its id, **every field a\n * `fromField` of its type reads**, and whatever else it carries. The type is\n * the middleware's to add.\n */\nexport type ObjectData<C extends ModelConfig, T extends ObjectTypeOf<C>> = {\n\treadonly id: string;\n} & { readonly [F in FieldsOf<C, T>]: string | null };\n\nexport type Awaitable<V> = V | Promise<V>;\n\n/**\n * The options of `permission()`: `ctx` required exactly when a condition of\n * the permission is reachable, as for `can()`.\n */\nexport type PermissionOptions<\n\tC extends ModelConfig,\n\tT extends ObjectTypeOf<C>,\n\tP extends string,\n\tO,\n> = {\n\t/**\n\t * Who asks. `c.var.user` — set by `session()` — when absent; pass it when\n\t * your users are not `janus()`'s. `null` is anonymous.\n\t */\n\treadonly subject?: (c: Context) => Awaitable<SubjectRef<C> | null>;\n} & ([CtxOf<C, T, P>] extends [never]\n\t? { readonly ctx?: never }\n\t: {\n\t\t\t/** The condition's context, read from the request and the loaded object. */\n\t\t\treadonly ctx: (c: Context, object: O) => Awaitable<CtxOf<C, T, P>>;\n\t\t});\n\n/**\n * The options argument: required exactly when `ctx` is. Loose when `T` or `P`\n * is its whole constraint — what the compiler falls back to when the object\n * type or the permission was wrong — so the error names that argument, not a\n * missing one; the rule `can()` follows.\n */\nexport type OptionsArgs<\n\tC extends ModelConfig,\n\tT extends ObjectTypeOf<C>,\n\tP extends string,\n\tO,\n> = [ObjectTypeOf<C>] extends [T]\n\t? IsSingle<ObjectTypeOf<C>> extends true\n\t\t? PermissionArgs<C, T, P, O>\n\t\t: [options?: LooseOptions]\n\t: PermissionArgs<C, T, P, O>;\n\n/** `OptionsArgs` once `T` is known right: loose when `P` is its whole constraint. */\ntype PermissionArgs<\n\tC extends ModelConfig,\n\tT extends ObjectTypeOf<C>,\n\tP extends string,\n\tO,\n> = [CheckableOf<C, T>] extends [P]\n\t? IsSingle<CheckableOf<C, T>> extends true\n\t\t? StrictArgs<C, T, P, O>\n\t\t: [options?: LooseOptions]\n\t: StrictArgs<C, T, P, O>;\n\ntype StrictArgs<\n\tC extends ModelConfig,\n\tT extends ObjectTypeOf<C>,\n\tP extends string,\n\tO,\n> = [CtxOf<C, T, P>] extends [never]\n\t? [options?: PermissionOptions<C, T, P, O>]\n\t: [options: PermissionOptions<C, T, P, O>];\n\ntype LooseOptions = {\n\treadonly subject?: (c: Context) => unknown;\n\treadonly ctx?: (c: Context, object: never) => unknown;\n};\n\ntype UnionToIntersection<U> = (\n\tU extends unknown\n\t\t? (union: U) => void\n\t\t: never\n) extends (intersection: infer I) => void\n\t? I\n\t: never;\n\n/** Whether `U` is one type rather than a union of several. */\ntype IsSingle<U> = [U] extends [UnionToIntersection<U>] ? true : false;\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 * The middleware that lets a request through only if its subject holds\n * `permission` on an object of `type`: it loads the object once, with `load`,\n * checks it with `access.can`, and hands it to the route as `c.var.object`.\n *\n * | The request | Answered |\n * | --- | --- |\n * | anonymous | 401, no body — before the object is loaded |\n * | `load` answers `null` | 404, no body |\n * | a denial | 403, no body |\n * | allowed | the route runs, `c.var.object` set |\n *\n * One per route: a second one throws a `TypeError`, since both would claim\n * `c.var.object`. A parent object is checked through an arrow in the model.\n *\n * **A failure throws**, as everywhere: a store that cannot answer is\n * `STORE_FAILED`, never a 403 — `janusErrors()` answers it 503.\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>(\n\taccess: Pick<Permissions<C>, 'can'>,\n\tpermission: P,\n\ttype: T,\n\tload: (c: Context) => Awaitable<O | null>,\n\t...options: OptionsArgs<C, T, P, O>\n): MiddlewareHandler<{\n\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\tVariables: { object: O };\n}>;\nexport function permission(\n\taccess: { readonly can: unknown },\n\tpermission: string,\n\ttype: string,\n\tload: (c: Context) => Awaitable<{ readonly id: string } | null>,\n\toptions: {\n\t\treadonly subject?: (\n\t\t\tc: Context,\n\t\t) => Awaitable<SubjectRef<ModelConfig> | null>;\n\t\treadonly ctx?: (c: Context, object: unknown) => Awaitable<unknown>;\n\t} = {},\n): MiddlewareHandler {\n\tconst can = access.can as LooseCan;\n\n\treturn async (c, next) => {\n\t\tif (c.get('object') !== undefined) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'permission(): c.var.object is already set — one permission() per route; check a parent object through an arrow in the model',\n\t\t\t);\n\t\t}\n\t\tconst subject =\n\t\t\toptions.subject === undefined ? userOf(c) : await options.subject(c);\n\t\tif (subject === null || subject === undefined) return c.body(null, 401);\n\n\t\tconst object = await load(c);\n\t\tif (object === null) return c.body(null, 404);\n\n\t\tconst allowed = await can(\n\t\t\tsubject,\n\t\t\tpermission,\n\t\t\tview(object, type),\n\t\t\toptions.ctx === undefined\n\t\t\t\t? undefined\n\t\t\t\t: { ctx: await options.ctx(c, object) },\n\t\t);\n\t\tif (!allowed) return c.body(null, 403);\n\n\t\tc.set('object', object);\n\t\tawait next();\n\t};\n}\n\n/**\n * A `load` reading one path parameter: `find(id)`, or `null` — a 404 — when\n * the route has no such parameter.\n *\n * ```ts\n * permission(access, 'view', 'record', byParam('id', (id) => records.find(id)))\n * ```\n */\nexport function byParam<O>(\n\tname: string,\n\tfind: (id: string) => Awaitable<O | null>,\n): (c: Context) => Awaitable<O | null> {\n\treturn (c) => {\n\t\tconst id = c.req.param(name);\n\t\treturn id === undefined ? null : find(id);\n\t};\n}\n\n/**\n * The object as `can()` reads it: `type` added, every other field read from\n * the loaded object itself — so a getter, a class's `#private` state or an\n * ORM document's accessors answer as they do in the route. A spread would\n * copy own enumerable fields only.\n */\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/** `c.var.user`, which `session()` sets — or a wiring error when nothing did. */\nfunction userOf(c: Context): SubjectRef<ModelConfig> | null {\n\tconst user: unknown = c.get('user');\n\tif (user === undefined) {\n\t\tthrow new TypeError(\n\t\t\t'permission(): c.var.user is not set — put session(auth) before it, or pass { subject }',\n\t\t);\n\t}\n\treturn user as SubjectRef<ModelConfig> | null;\n}\n",
5
+ "import type {\n\tCheckableOf,\n\tCtxOf,\n\tFieldsOf,\n\tModelConfig,\n\tObjectTypeOf,\n\tPermissions,\n\tSubjectRef,\n} from '@nxgt/janus/permissions';\nimport type { Context, MiddlewareHandler } from 'hono';\n\n/**\n * An object of type `T` as the application loads it: its id, **every field a\n * `fromField` of its type reads**, and whatever else it carries. The type is\n * the middleware's to add.\n */\nexport type ObjectData<C extends ModelConfig, T extends ObjectTypeOf<C>> = {\n\treadonly id: string;\n} & { readonly [F in FieldsOf<C, T>]: string | null };\n\nexport type Awaitable<V> = V | Promise<V>;\n\n/**\n * The options of `permission()`: `ctx` required exactly when a condition of\n * the permission is reachable, as for `can()`.\n */\nexport type PermissionOptions<\n\tC extends ModelConfig,\n\tT extends ObjectTypeOf<C>,\n\tP extends string,\n\tO,\n> = {\n\t/**\n\t * Who asks. `c.var.user` — set by `session()` — when absent; pass it when\n\t * your users are not `janus()`'s. `null` is anonymous.\n\t */\n\treadonly subject?: (c: Context) => Awaitable<SubjectRef<C> | null>;\n} & ([CtxOf<C, T, P>] extends [never]\n\t? { readonly ctx?: never }\n\t: {\n\t\t\t/** The condition's context, read from the request and the loaded object. */\n\t\t\treadonly ctx: (c: Context, object: O) => Awaitable<CtxOf<C, T, P>>;\n\t\t});\n\n/**\n * The options argument: required exactly when `ctx` is. Loose when `T` or `P`\n * is its whole constraint — what the compiler falls back to when the object\n * type or the permission was wrong — so the error names that argument, not a\n * missing one; the rule `can()` follows.\n */\nexport type OptionsArgs<\n\tC extends ModelConfig,\n\tT extends ObjectTypeOf<C>,\n\tP extends string,\n\tO,\n> = [ObjectTypeOf<C>] extends [T]\n\t? IsSingle<ObjectTypeOf<C>> extends true\n\t\t? PermissionArgs<C, T, P, O>\n\t\t: [options?: LooseOptions]\n\t: PermissionArgs<C, T, P, O>;\n\n/** `OptionsArgs` once `T` is known right: loose when `P` is its whole constraint. */\ntype PermissionArgs<\n\tC extends ModelConfig,\n\tT extends ObjectTypeOf<C>,\n\tP extends string,\n\tO,\n> = [CheckableOf<C, T>] extends [P]\n\t? IsSingle<CheckableOf<C, T>> extends true\n\t\t? StrictArgs<C, T, P, O>\n\t\t: [options?: LooseOptions]\n\t: StrictArgs<C, T, P, O>;\n\ntype StrictArgs<\n\tC extends ModelConfig,\n\tT extends ObjectTypeOf<C>,\n\tP extends string,\n\tO,\n> = [CtxOf<C, T, P>] extends [never]\n\t? [options?: PermissionOptions<C, T, P, O>]\n\t: [options: PermissionOptions<C, T, P, O>];\n\ntype LooseOptions = {\n\treadonly subject?: (c: Context) => unknown;\n\treadonly ctx?: (c: Context, object: never) => unknown;\n};\n\ntype UnionToIntersection<U> = (\n\tU extends unknown\n\t\t? (union: U) => void\n\t\t: never\n) extends (intersection: infer I) => void\n\t? I\n\t: never;\n\n/** Whether `U` is one type rather than a union of several. */\ntype IsSingle<U> = [U] extends [UnionToIntersection<U>] ? true : false;\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 * The middleware that lets a request through only if its subject holds\n * `permission` on an object of `type`: it loads the object once, with `load`,\n * checks it with `access.can`, and hands it to the route as `c.var.object`.\n *\n * | The request | Answered |\n * | --- | --- |\n * | anonymous | 401, no body — before the object is loaded |\n * | `load` answers `null` | 404, no body |\n * | a denial | 403, no body |\n * | allowed | the route runs, `c.var.object` set |\n *\n * One per route: a second one throws a `TypeError`, since both would claim\n * `c.var.object`. A parent object is checked through an arrow in the model.\n *\n * **A failure throws**, as everywhere: a store that cannot answer is\n * `STORE_FAILED`, never a 403 — `janusErrors()` answers it 503.\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>(\n\taccess: Pick<Permissions<C>, 'can'>,\n\tpermission: P,\n\ttype: T,\n\tload: (c: Context) => Awaitable<O | null>,\n\t...options: OptionsArgs<C, T, P, O>\n): MiddlewareHandler<{\n\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\tVariables: { object: O };\n}>;\nexport function permission(\n\taccess: { readonly can: unknown },\n\tpermission: string,\n\ttype: string,\n\tload: (c: Context) => Awaitable<{ readonly id: string } | null>,\n\toptions: {\n\t\treadonly subject?: (\n\t\t\tc: Context,\n\t\t) => Awaitable<SubjectRef<ModelConfig> | null>;\n\t\treadonly ctx?: (c: Context, object: unknown) => Awaitable<unknown>;\n\t} = {},\n): MiddlewareHandler {\n\tconst can = access.can as LooseCan;\n\n\treturn async (c, next) => {\n\t\tif (c.get('object') !== undefined) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'permission(): c.var.object is already set — one permission() per route; check a parent object through an arrow in the model',\n\t\t\t);\n\t\t}\n\t\tconst subject =\n\t\t\toptions.subject === undefined ? userOf(c) : await options.subject(c);\n\t\tif (subject === null || subject === undefined) return c.body(null, 401);\n\n\t\tconst object = await load(c);\n\t\tif (object === null) return c.body(null, 404);\n\n\t\tconst allowed = await can(\n\t\t\tsubject,\n\t\t\tpermission,\n\t\t\tview(object, type),\n\t\t\toptions.ctx === undefined\n\t\t\t\t? undefined\n\t\t\t\t: { ctx: await options.ctx(c, object) },\n\t\t);\n\t\tif (!allowed) return c.body(null, 403);\n\n\t\tc.set('object', object);\n\t\tawait next();\n\t\treturn undefined;\n\t};\n}\n\n/**\n * A `load` reading one path parameter: `find(id)`, or `null` — a 404 — when\n * the route has no such parameter.\n *\n * ```ts\n * permission(access, 'view', 'record', byParam('id', (id) => records.find(id)))\n * ```\n */\nexport function byParam<O>(\n\tname: string,\n\tfind: (id: string) => Awaitable<O | null>,\n): (c: Context) => Awaitable<O | null> {\n\treturn (c) => {\n\t\tconst id = c.req.param(name);\n\t\treturn id === undefined ? null : find(id);\n\t};\n}\n\n/**\n * The object as `can()` reads it: `type` added, every other field read from\n * the loaded object itself — so a getter, a class's `#private` state or an\n * ORM document's accessors answer as they do in the route. A spread would\n * copy own enumerable fields only.\n */\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/** `c.var.user`, which `session()` sets — or a wiring error when nothing did. */\nfunction userOf(c: Context): SubjectRef<ModelConfig> | null {\n\tconst user: unknown = c.get('user');\n\tif (user === undefined) {\n\t\tthrow new TypeError(\n\t\t\t'permission(): c.var.user is not set — put session(auth) before it, or pass { subject }',\n\t\t);\n\t}\n\treturn user as SubjectRef<ModelConfig> | null;\n}\n",
6
6
  "import type { MiddlewareHandler } from 'hono';\n\n/** What `provide()` takes: the `janus()` instance, the `permissions()` one, or both. */\nexport interface Instances {\n\treadonly auth?: object;\n\treadonly access?: object;\n}\n\n/**\n * The middleware that puts the instances on the context — `c.var.auth`, and\n * `c.var.access` — so a route writes users and tuples through them rather\n * than through a module import: `c.var.access.grant(record, 'owners', user)`.\n *\n * Only what is passed is set, and typed.\n */\nexport function provide<const I extends Instances>(\n\tinstances: I,\n): MiddlewareHandler<{\n\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\tVariables: { [K in keyof I & keyof Instances]: I[K] };\n}> {\n\treturn async (c, next) => {\n\t\tif (instances.auth !== undefined) c.set('auth', instances.auth);\n\t\tif (instances.access !== undefined) c.set('access', instances.access);\n\t\tawait next();\n\t};\n}\n",
7
- "import type { Session, SharedApi } from '@nxgt/janus';\nimport type { Context, MiddlewareHandler } from 'hono';\nimport { getCookie } from 'hono/cookie';\nimport { type DeviceCookieOptions, sendDevice } from './device';\n\n/** Anything `janus()` answered: the part of it this package calls. */\nexport type Auth<U extends { readonly type: string }> = Pick<\n\tSharedApi<U>,\n\t'authenticate' | 'signOut' | 'cookie'\n>;\n\n/** The users an `auth` instance knows, as a union narrowed by `user.type`. */\nexport type UserOfAuth<A> = A extends Auth<infer U> ? U : never;\n\n/**\n * What `session()` sets on the context: `c.var.user` and `c.var.session`,\n * both `null` for an anonymous request unless the route requires a user.\n *\n * `new Hono<SessionEnv<typeof auth>>()` types every route of an app that\n * wires `app.use(session(auth))` once.\n */\nexport type SessionEnv<\n\tA,\n\tT extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n\tRequired extends boolean = false,\n> = {\n\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\tVariables: Required extends true\n\t\t? {\n\t\t\t\tuser: Extract<UserOfAuth<A>, { readonly type: T }>;\n\t\t\t\tsession: Session;\n\t\t\t}\n\t\t: {\n\t\t\t\tuser: Extract<UserOfAuth<A>, { readonly type: T }> | null;\n\t\t\t\tsession: Session | null;\n\t\t\t};\n};\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, with no body, and the\n\t * route never runs — so `c.var.user` is never `null` in it. Only a literal\n\t * `true` types it so: a computed `boolean` leaves `c.var.user` nullable.\n\t */\n\treadonly required?: boolean;\n}\n\n/**\n * The middleware that reads who a request belongs to — `auth.authenticate`\n * on the request — and sets `c.var.user` and `c.var.session`.\n *\n * **An outage is not anonymous**: a store that cannot answer rejects with\n * `STORE_FAILED`, and this middleware lets it through to `app.onError` —\n * `janusErrors()` answers it 503, never 401.\n *\n * A session renewed in passing is sent again as a cookie, after the route\n * ran — but only to a request that presented it as one: a client that sends\n * `Authorization: Bearer` is not handed a cookie it never asked for. And\n * never over a session cookie the route set itself — `sendSession` after a\n * sign-in, `signOut`'s clearing — which a browser would otherwise overwrite.\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): MiddlewareHandler<SessionEnv<A, T, true>>;\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): MiddlewareHandler<SessionEnv<A, T>>;\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(auth: A, options: SessionOptions<T>): MiddlewareHandler<SessionEnv<A, T>>;\nexport function session(\n\tauth: Auth<{ readonly type: string }>,\n\toptions: SessionOptions<string> = {},\n): MiddlewareHandler {\n\treturn async (c, next) => {\n\t\tconst current = await auth.authenticate(\n\t\t\tc.req.raw,\n\t\t\toptions.type === undefined ? undefined : { type: options.type },\n\t\t);\n\n\t\tif (current === null && options.required === true) {\n\t\t\treturn c.body(null, 401);\n\t\t}\n\t\tc.set('user', current?.user ?? null);\n\t\tc.set('session', current?.session ?? null);\n\n\t\tawait next();\n\n\t\t// The route's own cookie wins: a sign-in or a sign-out behind this\n\t\t// middleware must not be undone by the session it replaced.\n\t\tconst name = `${auth.cookie.name}=`;\n\t\tif (\n\t\t\tcurrent?.renewed === true &&\n\t\t\tgetCookie(c, auth.cookie.name) === current.token &&\n\t\t\t!c.res.headers.getSetCookie().some((value) => value.startsWith(name))\n\t\t) {\n\t\t\tsendSession(c, auth, current);\n\t\t}\n\t};\n}\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`, or anything else that\n * answered a session token and its session — and answers the user. Appends: a\n * cookie set before stays.\n *\n * When the answer carries a `deviceToken` — the sign-in was given a\n * `device` — the device cookie is set too, or set again so it lasts another\n * four hundred days.\n *\n * ```ts\n * const signedIn = await auth.signIn({ email, password }, { device: deviceOf(c) });\n * const user = sendSession(c, auth, signedIn);\n * return c.json({ id: user.id }); // the token is in the cookie, never in the body\n * ```\n */\nexport function sendSession<U>(\n\tc: Context,\n\tauth: Pick<Auth<{ readonly type: string }>, 'cookie'>,\n\tsignedIn: {\n\t\treadonly token: string;\n\t\treadonly session: Session;\n\t\treadonly user: U;\n\t\treadonly deviceToken?: string | null;\n\t},\n\toptions: SendSessionOptions = {},\n): U {\n\tc.header(\n\t\t'Set-Cookie',\n\t\tauth.cookie.serialize(signedIn.token, signedIn.session),\n\t\t{\n\t\t\tappend: true,\n\t\t},\n\t);\n\tif (typeof signedIn.deviceToken === 'string') {\n\t\tsendDevice(c, 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 * `false` when the request presented no session, or an unknown one.\n */\nexport async function signOut(\n\tc: Context,\n\tauth: Pick<Auth<{ readonly type: string }>, 'signOut' | 'cookie'>,\n): Promise<boolean> {\n\tconst revoked = await auth.signOut(c.req.raw);\n\tc.header('Set-Cookie', auth.cookie.clear(), { append: true });\n\treturn revoked;\n}\n",
7
+ "import type { Session, SharedApi } from '@nxgt/janus';\nimport type { Context, MiddlewareHandler } from 'hono';\nimport { getCookie } from 'hono/cookie';\nimport { type DeviceCookieOptions, sendDevice } from './device';\n\n/** Anything `janus()` answered: the part of it this package calls. */\nexport type Auth<U extends { readonly type: string }> = Pick<\n\tSharedApi<U>,\n\t'authenticate' | 'signOut' | 'cookie'\n>;\n\n/** The users an `auth` instance knows, as a union narrowed by `user.type`. */\nexport type UserOfAuth<A> = A extends Auth<infer U> ? U : never;\n\n/**\n * What `session()` sets on the context: `c.var.user` and `c.var.session`,\n * both `null` for an anonymous request unless the route requires a user.\n *\n * `new Hono<SessionEnv<typeof auth>>()` types every route of an app that\n * wires `app.use(session(auth))` once.\n */\nexport type SessionEnv<\n\tA,\n\tT extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n\tRequired extends boolean = false,\n> = {\n\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\tVariables: Required extends true\n\t\t? {\n\t\t\t\tuser: Extract<UserOfAuth<A>, { readonly type: T }>;\n\t\t\t\tsession: Session;\n\t\t\t}\n\t\t: {\n\t\t\t\tuser: Extract<UserOfAuth<A>, { readonly type: T }> | null;\n\t\t\t\tsession: Session | null;\n\t\t\t};\n};\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, with no body, and the\n\t * route never runs — so `c.var.user` is never `null` in it. Only a literal\n\t * `true` types it so: a computed `boolean` leaves `c.var.user` nullable.\n\t */\n\treadonly required?: boolean;\n}\n\n/**\n * The middleware that reads who a request belongs to — `auth.authenticate`\n * on the request — and sets `c.var.user` and `c.var.session`.\n *\n * **An outage is not anonymous**: a store that cannot answer rejects with\n * `STORE_FAILED`, and this middleware lets it through to `app.onError` —\n * `janusErrors()` answers it 503, never 401.\n *\n * A session renewed in passing is sent again as a cookie, after the route\n * ran — but only to a request that presented it as one: a client that sends\n * `Authorization: Bearer` is not handed a cookie it never asked for. And\n * never over a session cookie the route set itself — `sendSession` after a\n * sign-in, `signOut`'s clearing — which a browser would otherwise overwrite.\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): MiddlewareHandler<SessionEnv<A, T, true>>;\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): MiddlewareHandler<SessionEnv<A, T>>;\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(auth: A, options: SessionOptions<T>): MiddlewareHandler<SessionEnv<A, T>>;\nexport function session(\n\tauth: Auth<{ readonly type: string }>,\n\toptions: SessionOptions<string> = {},\n): MiddlewareHandler {\n\treturn async (c, next) => {\n\t\tconst current = await auth.authenticate(\n\t\t\tc.req.raw,\n\t\t\toptions.type === undefined ? undefined : { type: options.type },\n\t\t);\n\n\t\tif (current === null && options.required === true) {\n\t\t\treturn c.body(null, 401);\n\t\t}\n\t\tc.set('user', current?.user ?? null);\n\t\tc.set('session', current?.session ?? null);\n\n\t\tawait next();\n\n\t\t// The route's own cookie wins: a sign-in or a sign-out behind this\n\t\t// middleware must not be undone by the session it replaced.\n\t\tconst name = `${auth.cookie.name}=`;\n\t\tif (\n\t\t\tcurrent?.renewed === true &&\n\t\t\tgetCookie(c, auth.cookie.name) === current.token &&\n\t\t\t!c.res.headers.getSetCookie().some((value) => value.startsWith(name))\n\t\t) {\n\t\t\tsendSession(c, auth, current);\n\t\t}\n\t\treturn undefined;\n\t};\n}\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`, or anything else that\n * answered a session token and its session — and answers the user. Appends: a\n * cookie set before stays.\n *\n * When the answer carries a `deviceToken` — the sign-in was given a\n * `device` — the device cookie is set too, or set again so it lasts another\n * four hundred days.\n *\n * ```ts\n * const signedIn = await auth.signIn({ email, password }, { device: deviceOf(c) });\n * const user = sendSession(c, auth, signedIn);\n * return c.json({ id: user.id }); // the token is in the cookie, never in the body\n * ```\n */\nexport function sendSession<U>(\n\tc: Context,\n\tauth: Pick<Auth<{ readonly type: string }>, 'cookie'>,\n\tsignedIn: {\n\t\treadonly token: string;\n\t\treadonly session: Session;\n\t\treadonly user: U;\n\t\treadonly deviceToken?: string | null;\n\t},\n\toptions: SendSessionOptions = {},\n): U {\n\tc.header(\n\t\t'Set-Cookie',\n\t\tauth.cookie.serialize(signedIn.token, signedIn.session),\n\t\t{\n\t\t\tappend: true,\n\t\t},\n\t);\n\tif (typeof signedIn.deviceToken === 'string') {\n\t\tsendDevice(c, 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 * `false` when the request presented no session, or an unknown one.\n */\nexport async function signOut(\n\tc: Context,\n\tauth: Pick<Auth<{ readonly type: string }>, 'signOut' | 'cookie'>,\n): Promise<boolean> {\n\tconst revoked = await auth.signOut(c.req.raw);\n\tc.header('Set-Cookie', auth.cookie.clear(), { append: true });\n\treturn revoked;\n}\n",
8
8
  "import type { Context } from 'hono';\nimport { getCookie, setCookie } from 'hono/cookie';\n\n/** The device cookie's name when none is given. */\nexport const DEVICE_COOKIE = 'janus-device';\n\n/** Four hundred days: the longest a browser keeps a cookie. */\nconst MAX_AGE = 400 * 24 * 60 * 60;\n\n/**\n * The long-lived cookie that holds the device token `@nxgt/janus` answers\n * as `deviceToken`. Every default is the strict one, as for the session\n * cookie; give `deviceOf` and `sendSession` the same options.\n */\nexport interface DeviceCookieOptions {\n\t/** `'janus-device'` when absent. */\n\treadonly name?: string;\n\treadonly domain?: string;\n\t/** `'/'` when absent. */\n\treadonly path?: string;\n\t/** `'Lax'` when absent. */\n\treadonly sameSite?: 'Lax' | 'Strict' | 'None';\n\t/** `true` when absent. */\n\treadonly secure?: boolean;\n\t/** In seconds. Four hundred days when absent — the longest a browser keeps one. */\n\treadonly maxAge?: number;\n}\n\n/**\n * The device token the request's cookie holds, or `null` when it holds none:\n * what a sign-in takes as `device`.\n *\n * ```ts\n * const signedIn = await auth.signIn({ email, password }, { device: deviceOf(c) });\n * ```\n */\nexport function deviceOf(\n\tc: Context,\n\toptions: DeviceCookieOptions = {},\n): string | null {\n\tconst token = getCookie(c, options.name ?? DEVICE_COOKIE);\n\treturn token === undefined || token === '' ? null : token;\n}\n\n/** Sets the device cookie, or sets it again so it lasts another `maxAge`. Appends. */\nexport function sendDevice(\n\tc: Context,\n\tdeviceToken: string,\n\toptions: DeviceCookieOptions = {},\n): void {\n\tsetCookie(c, options.name ?? DEVICE_COOKIE, deviceToken, {\n\t\tpath: options.path ?? '/',\n\t\t...(options.domain === undefined ? {} : { domain: options.domain }),\n\t\tsameSite: options.sameSite ?? 'Lax',\n\t\tsecure: options.secure ?? true,\n\t\thttpOnly: true,\n\t\tmaxAge: options.maxAge ?? MAX_AGE,\n\t});\n}\n",
9
- "import type { Session } from '@nxgt/janus';\nimport type {\n\tCheckableOf,\n\tModelConfig,\n\tObjectTypeOf,\n\tPermissions,\n} from '@nxgt/janus/permissions';\nimport type { Context, MiddlewareHandler } from 'hono';\nimport {\n\ttype Awaitable,\n\ttype ObjectData,\n\ttype OptionsArgs,\n\tpermission,\n} from './permission';\nimport { provide } from './provide';\nimport {\n\ttype Auth,\n\ttype SendSessionOptions,\n\ttype SessionEnv,\n\ttype SessionOptions,\n\tsendSession,\n\tsession,\n\tsignOut,\n\ttype UserOfAuth,\n} from './session';\n\n/** `session(auth, options?)`, with `auth` bound: the same three forms. */\nexport interface BoundSession<A> {\n\t<const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(\n\t\toptions: SessionOptions<T> & { readonly required: true },\n\t): MiddlewareHandler<SessionEnv<A, T, true>>;\n\t<const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(\n\t\toptions?: SessionOptions<T> & { readonly required?: false },\n\t): MiddlewareHandler<SessionEnv<A, T>>;\n\t<const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(\n\t\toptions: SessionOptions<T>,\n\t): MiddlewareHandler<SessionEnv<A, T>>;\n}\n\n/** `permission(access, …)`, with `access` bound. */\nexport type BoundPermission<C extends ModelConfig> = <\n\tconst T extends ObjectTypeOf<C>,\n\tconst P extends CheckableOf<C, T>,\n\tO extends ObjectData<C, T>,\n>(\n\tpermission: P,\n\ttype: T,\n\tload: (c: Context) => Awaitable<O | null>,\n\t...options: OptionsArgs<C, T, P, O>\n) => MiddlewareHandler<{\n\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\tVariables: { object: O };\n}>;\n\n/** The functions of this package that take `auth`, with `auth` bound. */\nexport interface BoundAuth<A> {\n\treadonly session: BoundSession<A>;\n\t/** `sendSession(c, auth, signedIn, options?)`: sets the cookies, answers the user. */\n\treadonly sendSession: <U>(\n\t\tc: Context,\n\t\tsignedIn: {\n\t\t\treadonly token: string;\n\t\t\treadonly session: Session;\n\t\t\treadonly user: U;\n\t\t\treadonly deviceToken?: string | null;\n\t\t},\n\t\toptions?: SendSessionOptions,\n\t) => U;\n\t/** `signOut(c, auth)`. */\n\treadonly signOut: (c: Context) => Promise<boolean>;\n}\n\n/** What `bindJanus()` takes: the `janus()` instance, the `permissions()` one, or both. */\nexport interface Bindable {\n\treadonly auth?: Auth<{ readonly type: string }>;\n\t/** What `permission()` takes: a `permissions()` instance, or its `can`. */\n\treadonly access?: { readonly can: unknown };\n}\n\n/**\n * What `bindJanus(instances)` answers: `provide()` always; `session`,\n * `sendSession` and `signOut` with `auth`; `permission` with `access`.\n */\nexport type Bound<I extends Bindable> = {\n\t/** `provide(instances)`: every instance bound, on the context. */\n\treadonly provide: () => MiddlewareHandler<{\n\t\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\t\tVariables: { [K in keyof I & keyof Bindable]: I[K] };\n\t}>;\n} & (I['auth'] extends Auth<{ readonly type: string }>\n\t? BoundAuth<I['auth']>\n\t: unknown) &\n\t(I['access'] extends Pick<Permissions<infer C>, 'can'>\n\t\t? { readonly permission: BoundPermission<C> }\n\t\t: unknown);\n\n/**\n * This package's functions with the instances bound once, so no route\n * repeats them:\n *\n * ```ts\n * const j = bindJanus({ auth, access });\n * const app = new Hono()\n * .use(j.session(), j.provide())\n * .get('/records/:id', j.permission('view', 'record', recordOf), (c) => c.json(c.var.object));\n * ```\n *\n * The same functions, typed the same way: `j.session({ required: true })` is\n * `session(auth, { required: true })`. Only what is given is bound — a\n * `permission` without `access` is absent from the type.\n */\nexport function bindJanus<const I extends Bindable>(instances: I): Bound<I> {\n\tconst { auth, access } = instances;\n\tconst bound: Record<string, unknown> = {\n\t\tprovide: () => provide(instances),\n\t};\n\tif (auth !== undefined) {\n\t\tbound.session = (options: SessionOptions<string> = {}) =>\n\t\t\tsession(auth, options);\n\t\tbound.sendSession = (\n\t\t\tc: Context,\n\t\t\tsignedIn: Parameters<typeof sendSession>[2],\n\t\t\toptions?: SendSessionOptions,\n\t\t) => sendSession(c, auth, signedIn, options);\n\t\tbound.signOut = (c: Context) => signOut(c, auth);\n\t}\n\tif (access !== undefined) {\n\t\tconst guard = permission as unknown as (\n\t\t\taccess: object,\n\t\t\t...rest: unknown[]\n\t\t) => MiddlewareHandler;\n\t\tbound.permission = (...rest: unknown[]) => guard(access, ...rest);\n\t}\n\treturn bound as Bound<I>;\n}\n",
10
- "import {\n\tJanusError,\n\ttype JanusErrorCode,\n\tstatusOf as janusStatusOf,\n} from '@nxgt/janus';\nimport type { Context, ErrorHandler } from 'hono';\nimport type { HTTPException } from 'hono/http-exception';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\n\n/**\n * The status each code deserves: `@nxgt/janus`'s own table, typed as the\n * `ContentfulStatusCode` `c.json()` takes. Kept as an export of its own so a\n * route that answers by hand reads the same status `janusErrors()` does.\n *\n * `STORE_FAILED` is 503 and nothing else — **an outage is not a negative\n * answer**, and a 401 or a 404 would tell every user they do not exist.\n */\nexport function statusOf(code: JanusErrorCode): ContentfulStatusCode {\n\treturn janusStatusOf(code);\n}\n\n/**\n * The body a refusal is answered with: its `code`, and only what the client\n * can act on — the fields that failed the schema, the policy's minimum, the\n * attempts a challenge has left, the seconds a throttled sign-in waits.\n * Never `reason`, `login`, a hash prefix or a cause: those are for your logs.\n */\nexport function bodyOf(error: JanusError): {\n\treadonly code: JanusErrorCode;\n\treadonly issues?: JanusError['issues'];\n\treadonly minLength?: number;\n\treadonly attemptsLeft?: number;\n\treadonly retryAfter?: number;\n} {\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\n/**\n * Hono's own answer to an error it was not told about, copied from its\n * default handler: the response of an `HTTPException` — merged with the\n * headers the route set before throwing — and a logged 500 for anything else.\n */\nfunction honoDefault(error: Error | HTTPException, c: Context): Response {\n\tif ('getResponse' in error) {\n\t\tconst response = error.getResponse();\n\t\treturn c.newResponse(response.body, response);\n\t}\n\tconsole.error(error);\n\treturn c.text('Internal Server Error', 500);\n}\n\n/** What `janusErrors()` takes. */\nexport interface JanusErrorsOptions {\n\t/**\n\t * Called with every `JanusError` answered 5xx — `STORE_FAILED`,\n\t * `UNSUPPORTED`, `PERMISSION_DEPTH`: the ones the server must fix, not the\n\t * client — before it is answered. Its `slot`, `operation`, `reason` and\n\t * `cause` are for your logs; the body never carries them.\n\t *\n\t * **It cannot stop the answer**: one that throws, or rejects, is a\n\t * `process.emitWarning`, and the 503 is sent all the same.\n\t */\n\treadonly report?: (error: JanusError, c: Context) => unknown;\n\t/** Every error that is not a `JanusError`. Hono's own handling when absent. */\n\treadonly fallback?: ErrorHandler;\n}\n\n/**\n * An `app.onError` handler: every `JanusError` answered with its status and\n * `bodyOf(error)` — and a throttled sign-in with a `Retry-After` header, its\n * `retryAfter` in seconds; anything else handed to `fallback` — Hono's own\n * behaviour when absent.\n *\n * Works for either side of `@nxgt/janus`: `can()`'s `STORE_FAILED` is a 503\n * here too, never a 403.\n *\n * ```ts\n * app.onError(janusErrors({ report: (error) => logger.error(error) }));\n * ```\n */\nexport function janusErrors(options: JanusErrorsOptions = {}): ErrorHandler {\n\tconst { report, fallback = honoDefault } = options;\n\treturn (error, c) => {\n\t\tif (!(error instanceof JanusError)) return fallback(error, c);\n\t\tconst status = statusOf(error.code);\n\t\tif (status >= 500 && report !== undefined) reportSafely(report, error, c);\n\t\tconst headers =\n\t\t\terror.retryAfter === undefined\n\t\t\t\t? undefined\n\t\t\t\t: { 'retry-after': String(error.retryAfter) };\n\t\treturn c.json(bodyOf(error), status, headers);\n\t};\n}\n\n/** `report`, whose own failure is a warning — never a lost answer. */\nfunction reportSafely(\n\treport: NonNullable<JanusErrorsOptions['report']>,\n\terror: JanusError,\n\tc: Context,\n): void {\n\tconst warn = (failure: unknown) =>\n\t\tprocess.emitWarning(\n\t\t\t`janusErrors: report failed on ${error.code}: ${failure instanceof Error ? failure.name : typeof failure}`,\n\t\t);\n\ttry {\n\t\t// A thenable of any make, as a native promise: its rejection warns too.\n\t\tPromise.resolve(report(error, c)).then(undefined, warn);\n\t} catch (failure) {\n\t\twarn(failure);\n\t}\n}\n",
11
- "import {\n\ttype Clock,\n\ttype Duration,\n\tparseDuration,\n\ttype Session,\n\tStepUpRequiredError,\n\tsystemClock,\n} from '@nxgt/janus';\nimport type { MiddlewareHandler } from 'hono';\n\n/** What `fresh()` takes besides `maxAge`. */\nexport interface FreshOptions {\n\t/** The clock given to `janus()`, when it is not the system's: `fixedClock` in a spec. */\n\treadonly clock?: Clock;\n}\n\n/**\n * The middleware that lets a route run only for a session that proved who\n * it is less than `maxAge` ago — signed in, or confirmed by\n * `auth.stepUp.confirm`, since. Behind `session(auth)`, which sets\n * `c.var.session`:\n *\n * ```ts\n * app.delete('/account', session(auth, { required: true }), fresh('10m'), deleteAccount);\n * ```\n *\n * A session older than that throws `STEP_UP_REQUIRED`, which `janusErrors()`\n * answers 403 with `{ code: 'STEP_UP_REQUIRED' }`: the client asks for a\n * step-up, then sends the request again. An anonymous request is answered\n * 401, with no body, as `session(auth, { required: true })` answers it.\n *\n * A `maxAge` that is not a duration is a `TypeError` here, when the app is\n * wired — not on the first request.\n * No `session()` before it is a `TypeError` at the first request — a\n * wiring error, not an anonymous 401 — as for `permission()`.\n */\nexport function fresh(\n\tmaxAge: Duration,\n\toptions: FreshOptions = {},\n): MiddlewareHandler<{\n\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\tVariables: { session: Session | null };\n}> {\n\tconst maxAgeMs = parseDuration(maxAge, 'fresh: maxAge');\n\treturn async (c, next) => {\n\t\tconst current = c.var.session;\n\t\tif (current === undefined) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'fresh(): c.var.session is not set — put session(auth) before it',\n\t\t\t);\n\t\t}\n\t\tif (current === null) return c.body(null, 401);\n\t\t// `assertFresh`'s rule, with a message naming the call written here.\n\t\tconst now = (options.clock ?? systemClock).now().getTime();\n\t\tif (now - current.authenticatedAt.getTime() >= maxAgeMs) {\n\t\t\tthrow new StepUpRequiredError(\n\t\t\t\t'fresh(): the session proved who it is longer ago than maxAge — confirm with a step-up',\n\t\t\t\t{ userId: current.userId },\n\t\t\t);\n\t\t}\n\t\tawait next();\n\t};\n}\n"
9
+ "import type { Session } from '@nxgt/janus';\nimport type {\n\tCheckableOf,\n\tModelConfig,\n\tObjectTypeOf,\n\tPermissions,\n} from '@nxgt/janus/permissions';\nimport type { Context, MiddlewareHandler } from 'hono';\nimport {\n\ttype Awaitable,\n\ttype ObjectData,\n\ttype OptionsArgs,\n\tpermission,\n} from './permission';\nimport { provide } from './provide';\nimport {\n\ttype Auth,\n\ttype SendSessionOptions,\n\ttype SessionEnv,\n\ttype SessionOptions,\n\tsendSession,\n\tsession,\n\tsignOut,\n\ttype UserOfAuth,\n} from './session';\n\n/** `session(auth, options?)`, with `auth` bound: the same three forms. */\nexport interface BoundSession<A> {\n\t<const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(\n\t\toptions: SessionOptions<T> & { readonly required: true },\n\t): MiddlewareHandler<SessionEnv<A, T, true>>;\n\t<const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(\n\t\toptions?: SessionOptions<T> & { readonly required?: false },\n\t): MiddlewareHandler<SessionEnv<A, T>>;\n\t<const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(\n\t\toptions: SessionOptions<T>,\n\t): MiddlewareHandler<SessionEnv<A, T>>;\n}\n\n/** `permission(access, …)`, with `access` bound. */\nexport type BoundPermission<C extends ModelConfig> = <\n\tconst T extends ObjectTypeOf<C>,\n\tconst P extends CheckableOf<C, T>,\n\tO extends ObjectData<C, T>,\n>(\n\tpermission: P,\n\ttype: T,\n\tload: (c: Context) => Awaitable<O | null>,\n\t...options: OptionsArgs<C, T, P, O>\n) => MiddlewareHandler<{\n\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\tVariables: { object: O };\n}>;\n\n/** The functions of this package that take `auth`, with `auth` bound. */\nexport interface BoundAuth<A> {\n\treadonly session: BoundSession<A>;\n\t/** `sendSession(c, auth, signedIn, options?)`: sets the cookies, answers the user. */\n\treadonly sendSession: <U>(\n\t\tc: Context,\n\t\tsignedIn: {\n\t\t\treadonly token: string;\n\t\t\treadonly session: Session;\n\t\t\treadonly user: U;\n\t\t\treadonly deviceToken?: string | null;\n\t\t},\n\t\toptions?: SendSessionOptions,\n\t) => U;\n\t/** `signOut(c, auth)`. */\n\treadonly signOut: (c: Context) => Promise<boolean>;\n}\n\n/** What `bindJanus()` takes: the `janus()` instance, the `permissions()` one, or both. */\nexport interface Bindable {\n\treadonly auth?: Auth<{ readonly type: string }>;\n\t/** What `permission()` takes: a `permissions()` instance, or its `can`. */\n\treadonly access?: { readonly can: unknown };\n}\n\n/**\n * What `bindJanus(instances)` answers: `provide()` always; `session`,\n * `sendSession` and `signOut` with `auth`; `permission` with `access`.\n */\nexport type Bound<I extends Bindable> = {\n\t/** `provide(instances)`: every instance bound, on the context. */\n\treadonly provide: () => MiddlewareHandler<{\n\t\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\t\tVariables: { [K in keyof I & keyof Bindable]: I[K] };\n\t}>;\n} & (I['auth'] extends Auth<{ readonly type: string }>\n\t? BoundAuth<I['auth']>\n\t: unknown) &\n\t(I['access'] extends Pick<Permissions<infer C>, 'can'>\n\t\t? { readonly permission: BoundPermission<C> }\n\t\t: unknown);\n\n/**\n * This package's functions with the instances bound once, so no route\n * repeats them:\n *\n * ```ts\n * const j = bindJanus({ auth, access });\n * const app = new Hono()\n * .use(j.session(), j.provide())\n * .get('/records/:id', j.permission('view', 'record', recordOf), (c) => c.json(c.var.object));\n * ```\n *\n * The same functions, typed the same way: `j.session({ required: true })` is\n * `session(auth, { required: true })`. Only what is given is bound — a\n * `permission` without `access` is absent from the type.\n */\nexport function bindJanus<const I extends Bindable>(instances: I): Bound<I> {\n\tconst { auth, access } = instances;\n\tconst bound: Record<string, unknown> = {\n\t\tprovide: () => provide(instances),\n\t};\n\tif (auth !== undefined) {\n\t\tbound['session'] = (options: SessionOptions<string> = {}) =>\n\t\t\tsession(auth, options);\n\t\tbound['sendSession'] = (\n\t\t\tc: Context,\n\t\t\tsignedIn: Parameters<typeof sendSession>[2],\n\t\t\toptions?: SendSessionOptions,\n\t\t) => sendSession(c, auth, signedIn, options);\n\t\tbound['signOut'] = (c: Context) => signOut(c, auth);\n\t}\n\tif (access !== undefined) {\n\t\tconst guard = permission as unknown as (\n\t\t\taccess: object,\n\t\t\t...rest: unknown[]\n\t\t) => MiddlewareHandler;\n\t\tbound['permission'] = (...rest: unknown[]) => guard(access, ...rest);\n\t}\n\treturn bound as Bound<I>;\n}\n",
10
+ "import {\n\tJanusError,\n\ttype JanusErrorCode,\n\tstatusOf as janusStatusOf,\n} from '@nxgt/janus';\nimport type { Context, ErrorHandler } from 'hono';\nimport type { HTTPException } from 'hono/http-exception';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\n\n/**\n * The status each code deserves: `@nxgt/janus`'s own table, typed as the\n * `ContentfulStatusCode` `c.json()` takes. Kept as an export of its own so a\n * route that answers by hand reads the same status `janusErrors()` does.\n *\n * `STORE_FAILED` is 503 and nothing else — **an outage is not a negative\n * answer**, and a 401 or a 404 would tell every user they do not exist.\n */\nexport function statusOf(code: JanusErrorCode): ContentfulStatusCode {\n\treturn janusStatusOf(code);\n}\n\n/**\n * The body a refusal is answered with: its `code`, and only what the client\n * can act on — the fields that failed the schema, the policy's minimum, the\n * attempts a challenge has left, the seconds a throttled sign-in or a\n * throttled request for an e-mail waits.\n * Never `reason`, `login`, a hash prefix or a cause: those are for your logs.\n */\nexport function bodyOf(error: JanusError): {\n\treadonly code: JanusErrorCode;\n\treadonly issues?: JanusError['issues'];\n\treadonly minLength?: number;\n\treadonly attemptsLeft?: number;\n\treadonly retryAfter?: number;\n} {\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\tcase 'MAIL_THROTTLED':\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\n/**\n * Hono's own answer to an error it was not told about, copied from its\n * default handler: the response of an `HTTPException` — merged with the\n * headers the route set before throwing — and a logged 500 for anything else.\n */\nfunction honoDefault(error: Error | HTTPException, c: Context): Response {\n\tif ('getResponse' in error) {\n\t\tconst response = error.getResponse();\n\t\treturn c.newResponse(response.body, response);\n\t}\n\tconsole.error(error);\n\treturn c.text('Internal Server Error', 500);\n}\n\n/** What `janusErrors()` takes. */\nexport interface JanusErrorsOptions {\n\t/**\n\t * Called with every `JanusError` answered 5xx — `STORE_FAILED`,\n\t * `UNSUPPORTED`, `PERMISSION_DEPTH`: the ones the server must fix, not the\n\t * client — before it is answered. Its `slot`, `operation`, `reason` and\n\t * `cause` are for your logs; the body never carries them.\n\t *\n\t * **It cannot stop the answer**: one that throws, or rejects, is a\n\t * `process.emitWarning`, and the 503 is sent all the same.\n\t */\n\treadonly report?: (error: JanusError, c: Context) => unknown;\n\t/** Every error that is not a `JanusError`. Hono's own handling when absent. */\n\treadonly fallback?: ErrorHandler;\n}\n\n/**\n * An `app.onError` handler: every `JanusError` answered with its status and\n * `bodyOf(error)` — and a throttled sign-in, or a request past its e-mails\n * (`MAIL_THROTTLED`, 429), with a `Retry-After` header, its `retryAfter` in\n * seconds; anything else handed to `fallback` — Hono's own\n * behaviour when absent.\n *\n * Works for either side of `@nxgt/janus`: `can()`'s `STORE_FAILED` is a 503\n * here too, never a 403.\n *\n * ```ts\n * app.onError(janusErrors({ report: (error) => logger.error(error) }));\n * ```\n */\nexport function janusErrors(options: JanusErrorsOptions = {}): ErrorHandler {\n\tconst { report, fallback = honoDefault } = options;\n\treturn (error, c) => {\n\t\tif (!(error instanceof JanusError)) return fallback(error, c);\n\t\tconst status = statusOf(error.code);\n\t\tif (status >= 500 && report !== undefined) reportSafely(report, error, c);\n\t\tconst headers =\n\t\t\terror.retryAfter === undefined\n\t\t\t\t? undefined\n\t\t\t\t: { 'retry-after': String(error.retryAfter) };\n\t\treturn c.json(bodyOf(error), status, headers);\n\t};\n}\n\n/** `report`, whose own failure is a warning — never a lost answer. */\nfunction reportSafely(\n\treport: NonNullable<JanusErrorsOptions['report']>,\n\terror: JanusError,\n\tc: Context,\n): void {\n\tconst warn = (failure: unknown) =>\n\t\tprocess.emitWarning(\n\t\t\t`janusErrors: report failed on ${error.code}: ${failure instanceof Error ? failure.name : typeof failure}`,\n\t\t);\n\ttry {\n\t\t// A thenable of any make, as a native promise: its rejection warns too.\n\t\tPromise.resolve(report(error, c)).then(undefined, warn);\n\t} catch (failure) {\n\t\twarn(failure);\n\t}\n}\n",
11
+ "import {\n\ttype Clock,\n\ttype Duration,\n\tparseDuration,\n\ttype Session,\n\tStepUpRequiredError,\n\tsystemClock,\n} from '@nxgt/janus';\nimport type { MiddlewareHandler } from 'hono';\n\n/** What `fresh()` takes besides `maxAge`. */\nexport interface FreshOptions {\n\t/** The clock given to `janus()`, when it is not the system's: `fixedClock` in a spec. */\n\treadonly clock?: Clock;\n}\n\n/**\n * The middleware that lets a route run only for a session that proved who\n * it is less than `maxAge` ago — signed in, or confirmed by\n * `auth.stepUp.confirm`, since. Behind `session(auth)`, which sets\n * `c.var.session`:\n *\n * ```ts\n * app.delete('/account', session(auth, { required: true }), fresh('10m'), deleteAccount);\n * ```\n *\n * A session older than that throws `STEP_UP_REQUIRED`, which `janusErrors()`\n * answers 403 with `{ code: 'STEP_UP_REQUIRED' }`: the client asks for a\n * step-up, then sends the request again. An anonymous request is answered\n * 401, with no body, as `session(auth, { required: true })` answers it.\n *\n * A `maxAge` that is not a duration is a `TypeError` here, when the app is\n * wired — not on the first request.\n * No `session()` before it is a `TypeError` at the first request — a\n * wiring error, not an anonymous 401 — as for `permission()`.\n */\nexport function fresh(\n\tmaxAge: Duration,\n\toptions: FreshOptions = {},\n): MiddlewareHandler<{\n\t// biome-ignore lint/style/useNamingConvention: Hono's `Env` names the key, not us.\n\tVariables: { session: Session | null };\n}> {\n\tconst maxAgeMs = parseDuration(maxAge, 'fresh: maxAge');\n\treturn async (c, next) => {\n\t\tconst current = c.var.session;\n\t\tif (current === undefined) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'fresh(): c.var.session is not set — put session(auth) before it',\n\t\t\t);\n\t\t}\n\t\tif (current === null) return c.body(null, 401);\n\t\t// `assertFresh`'s rule, with a message naming the call written here.\n\t\tconst now = (options.clock ?? systemClock).now().getTime();\n\t\tif (now - current.authenticatedAt.getTime() >= maxAgeMs) {\n\t\t\tthrow new StepUpRequiredError(\n\t\t\t\t'fresh(): the session proved who it is longer ago than maxAge — confirm with a step-up',\n\t\t\t\t{ userId: current.userId },\n\t\t\t);\n\t\t}\n\t\tawait next();\n\t\treturn undefined;\n\t};\n}\n"
12
12
  ],
13
- "mappings": "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",
14
- "debugId": "21C4E2121FB06E7D64756E2164756E21",
13
+ "mappings": "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",
14
+ "debugId": "3BB1888C6A6D634864756E2164756E21",
15
15
  "names": []
16
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"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"}
@@ -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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"}
@@ -177,6 +177,19 @@ retryAfter }` with a `Retry-After` header, the seconds until the next
177
177
  window; the route needs no code of its own. `janus({ signIn: { throttle } })`
178
178
  changes the limit ([passwords](https://github.com/softistx/nxgt-janus/blob/develop/packages/janus/docs/guide/passwords.md#password-guessing-is-throttled)).
179
179
 
180
+ **The requests that send an e-mail are throttled too.** Past five requests
181
+ for one address (or one user, for `verifyEmail.send` and an e-mailed
182
+ `stepUp.request`) in a 10-minute window, `@nxgt/janus` throws
183
+ `MAIL_THROTTLED` and sends nothing; `janusErrors()` answers it
184
+ `429 { code: 'MAIL_THROTTLED', retryAfter }` with a `Retry-After` header.
185
+ **A refused request spends and rotates nothing**: the last link or code sent
186
+ still works, so tell the visitor to use the last e-mail they received, or to
187
+ wait `retryAfter` seconds. `janus({ mail: { throttle } })` changes the limit;
188
+ keep its `window` no longer than the shortest `tokens.*` lifetime you use.
189
+ `@nxgt/janus` never sees IP addresses, so add a per-IP ceiling in front of
190
+ these routes with `@nxgt/redis` rate limits
191
+ ([example](https://github.com/softistx/nxgt-janus/blob/develop/packages/janus/docs/guide/email-flows.md#requests-that-send-e-mail-are-throttled)).
192
+
180
193
  What the throttle does not see is one password tried against many logins:
181
194
  limit the route **per client address** as well, with the limiter you already
182
195
  run, and a `changePassword` route per user — it compares the current
@@ -210,6 +223,7 @@ The refusals need no `try`: `janusErrors()` answers them.
210
223
  | `PASSWORD_TOO_SHORT` | 400 `{ code, minLength }` |
211
224
  | `LOGIN_TAKEN` | 409 `{ code }` |
212
225
  | `CREDENTIALS_INVALID` | 401 `{ code }` — the same for an unknown login and a wrong password; `{ code, retryAfter }` and a `Retry-After` header once the login is throttled |
226
+ | `MAIL_THROTTLED` | 429 `{ code, retryAfter }` and a `Retry-After` header — a request that sends an e-mail, past its attempts in the window |
213
227
  | `USER_INACTIVE` | 403 `{ code }` — only told to somebody who gave the right password |
214
228
 
215
229
  A bearer client — a mobile app — keeps its session token itself and sends it
@@ -401,8 +415,11 @@ cookie, would tell anyone which addresses have an account. The decoy is 32
401
415
  random bytes, the shape of a real challenge; confirming it is
402
416
  `TOKEN_UNKNOWN`. `maxAge: 600` is the default ten minutes of
403
417
  `tokens.signInCode`, written out so both cookies match. Not awaiting the
404
- mailer keeps the answer's time from telling either. Rate-limit this route
405
- per address and per client: every call sends an e-mail.
418
+ mailer keeps the answer's time from telling either. `@nxgt/janus` throttles
419
+ each address — five requests per 10 minutes, then `MAIL_THROTTLED`, which
420
+ `janusErrors()` answers 429 with `Retry-After` (the last code sent still
421
+ works); rate-limit the route per client yourself, since one client can ask for
422
+ many addresses.
406
423
 
407
424
  The code route still tells a decoy apart: a wrong code against it is 400
408
425
  `TOKEN_UNKNOWN`, against a real challenge 401 `CODE_INVALID` with
@@ -523,9 +540,9 @@ app.post('/sign-in/link', csrf({ origin: ORIGIN }), async (c) => {
523
540
  - **The request route answers `202` whoever asked.** `magicLink.request`
524
541
  answers `null` for an address nobody holds and for an inactive user. With
525
542
  a code, the route needs a decoy challenge. Nothing of a link reaches the
526
- visitor, so here there is no decoy to forge. Rate-limit the route per
527
- address and per client: every call sends an e-mail and cancels the link
528
- sent before it.
543
+ visitor, so here there is no decoy to forge. Every call sends an e-mail
544
+ and cancels the link sent before it: `@nxgt/janus` throttles each address
545
+ (`MAIL_THROTTLED`, 429 with `Retry-After`), and per client stays yours.
529
546
  - **The page echoes only a token of the token's shape.** Anything else
530
547
  written into the HTML is a cross-site scripting hole. Use a button, not a
531
548
  script that submits the form by itself: a scanner that runs scripts would
package/docs/roadmap.md CHANGED
@@ -36,6 +36,9 @@ Nothing yet.
36
36
 
37
37
  Newest first; the [CHANGELOG](../CHANGELOG.md) holds every release.
38
38
 
39
+ - **A throttled e-mail request's `retryAfter`, v0.7.0** — `janusErrors()`
40
+ and `bodyOf()` answer `@nxgt/janus`'s `MAIL_THROTTLED` with 429, `retryAfter`
41
+ in the body and a `Retry-After` header. Needs `@nxgt/janus` 0.18.0.
39
42
  - **The device cookie, v0.6.0** — `deviceOf(c)` reads the device token a
40
43
  browser holds, for a sign-in's `device`, and `sendSession` sets it — or
41
44
  sets it again for another 400 days — whenever `@nxgt/janus`'s answer
@@ -37,6 +37,7 @@ for what causes each.
37
37
  - [`400 {"code":"HASH_UNSUPPORTED"}` on sign-in](#400-codehash_unsupported-on-sign-in)
38
38
  - [`401 {"code":"CODE_INVALID","attemptsLeft":<n>}` on the code form](#401-codecode_invalidattemptsleftn-on-the-code-form) — a second factor's, or the code sent by e-mail
39
39
  - [`401 {"code":"CREDENTIALS_INVALID","retryAfter":<n>}` with the right password](#401-codecredentials_invalidretryaftern-with-the-right-password)
40
+ - [`429 {"code":"MAIL_THROTTLED","retryAfter":<n>}` on a request for an e-mail](#429-codemail_throttledretryaftern-on-a-request-for-an-e-mail)
40
41
  - [`403 Forbidden` on `POST /sign-in/link`](#403-forbidden-on-post-sign-inlink) — Hono's `csrf()`, not `janusErrors()`
41
42
  - [`409 {"code":"SECOND_FACTOR_ACTIVE"}` or `409 {"code":"SECOND_FACTOR_NOT_ENROLLED"}`](#409-codesecond_factor_active-or-409-codesecond_factor_not_enrolled)
42
43
  - [The browser never sends the cookie back](#the-browser-never-sends-the-cookie-back)
@@ -496,6 +497,38 @@ To change the limit, `janus({ signIn: { throttle: { attempts, window } } })`;
496
497
  in a test, advance the clock past `retryAfter`, or wire
497
498
  `signIn: { throttle: false }`.
498
499
 
500
+ ### `429 {"code":"MAIL_THROTTLED","retryAfter":<n>}` on a request for an e-mail
501
+
502
+ With a `Retry-After: <n>` header.
503
+
504
+ **When:** a route that asks for an e-mail — a sign-in link or code, a
505
+ password reset, an address check, an e-mailed step-up — after five requests
506
+ for the same address (the same user, for the last two) in the current
507
+ 10-minute window.
508
+
509
+ **Why:** `@nxgt/janus` throttles the requests that hand out something to
510
+ e-mail, on by default, each flow on its own; an unknown address is counted
511
+ and refused alike, so nothing is revealed. `retryAfter` is the seconds until
512
+ the window ends. **A refused request spends and rotates nothing**: nothing was
513
+ sent or issued, and the last link or code sent, by anyone's request, still
514
+ works until it expires.
515
+
516
+ **Fix:** tell the visitor to use the last e-mail they received, or to wait:
517
+
518
+ ```ts
519
+ const response = await fetch('/sign-in/email', { method: 'POST', body });
520
+ if (response.status === 429) {
521
+ const { retryAfter } = await response.json();
522
+ showWait(`Check your inbox: the last e-mail we sent still works. You can ask for a new one in ${Math.ceil(retryAfter / 60)} min.`);
523
+ }
524
+ ```
525
+
526
+ To change the limit, `janus({ mail: { throttle: { attempts, window } } })`
527
+ (keep `window` no longer than the shortest `tokens.*` lifetime you use, or the
528
+ last code can expire first); `@nxgt/janus` never sees IP addresses, so add a
529
+ per-IP ceiling with `@nxgt/redis` rate limits. In a test, advance the clock past `retryAfter`, or wire
530
+ `mail: { throttle: false }`.
531
+
499
532
  ### `403 Forbidden` on `POST /sign-in/link`
500
533
 
501
534
  The body is the text `Forbidden`, not a `{ code }`. It is not a `JanusError`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/janus-hono",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Hono middleware for @nxgt/janus: the signed-in user on every request, the session cookie, routes guarded by a permission, and every error as its status",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -49,13 +49,13 @@
49
49
  ]
50
50
  },
51
51
  "devDependencies": {
52
- "@nxgt/janus": "^0.17.0",
52
+ "@nxgt/janus": "^0.19.0",
53
53
  "@types/bun": "^1.4.2",
54
54
  "hono": "^4.13.9",
55
55
  "zod": "^4.6.5"
56
56
  },
57
57
  "peerDependencies": {
58
- "@nxgt/janus": "^0.17.0",
58
+ "@nxgt/janus": "^0.19.0",
59
59
  "hono": "^4.13.4",
60
60
  "typescript": "^6.0.3"
61
61
  }