mikser-io-auth 0.7.0 → 0.9.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
@@ -227,6 +227,44 @@ keeps them: a registered client and its refresh token exist only because a
227
227
  human completed a sign-in once, and they are not something the working folder
228
228
  can rebuild.
229
229
 
230
+ ### Minted tokens
231
+
232
+ `auth()` publishes a minting surface at `runtime.options.auth`, so another
233
+ plugin can hand out a credential **narrower than the caller's own** without
234
+ importing this package:
235
+
236
+ ```js
237
+ runtime.options.auth.mint({
238
+ subject: principal.subject,
239
+ capabilities: principal.capabilities, // what they already hold
240
+ request: ['webdav:media', 'webdav:media:write'],
241
+ ttlSec: 300,
242
+ purpose: 'webdav:media (write)',
243
+ })
244
+ ```
245
+
246
+ The scopes are the **intersection** of `request` and `capabilities`. This can
247
+ only ever narrow: there is no argument that widens anyone's reach, and asking
248
+ for a scope the caller does not hold is refused with the missing one named
249
+ rather than trimmed silently. A caller whose `capabilities` are `null` — a
250
+ static token, not capability-scoped — is refused outright, because it cannot
251
+ delegate what it cannot enumerate.
252
+
253
+ A minted token carries a **`jti`** and is recorded in `mikser_auth_minted`, which
254
+ makes it revokable before it expires — the one thing an ordinary JWT is not, and
255
+ the thing that matters for a credential handed to a machine that logs its own
256
+ output. `revokeMinted(jti)` kills one; `listMinted(subject)` is the audit view.
257
+ Every mint is logged with subject, purpose, scopes and ttl.
258
+
259
+ Verification **fails closed**: a token with a `jti` whose row is missing, or
260
+ whose checker was never wired, is rejected. Losing the record must revoke, never
261
+ un-revoke. Session tokens carry no `jti`, so they never touch this table and
262
+ gain no new failure mode.
263
+
264
+ Minted tokens are **not refreshable**. Expiry is the revocation mechanism, so
265
+ they are deliberately short and a caller mints again rather than renewing one
266
+ that has been sitting in a transcript.
267
+
230
268
  ### When an access token expires
231
269
 
232
270
  Access tokens are short (`ttl`, default `1h`) and refresh tokens are long, so
package/lib/tokens.js CHANGED
@@ -13,7 +13,7 @@ export async function issueToken({ key, issuer, audience, subject, capabilities
13
13
  // list, and a client library will parse it as one. The row filter travels
14
14
  // as a private claim so the two never collide. It is signed, so a client
15
15
  // cannot widen its own reach by editing it.
16
- return new SignJWT({
16
+ const signer = new SignJWT({
17
17
  scope: capabilities.join(' '),
18
18
  ...(scope ? { mks_scope: scope } : {}),
19
19
  })
@@ -23,7 +23,7 @@ export async function issueToken({ key, issuer, audience, subject, capabilities
23
23
  .setAudience(audience)
24
24
  .setSubject(subject)
25
25
  .setExpirationTime(ttl)
26
- .sign(key.privateKey)
26
+ return signer.sign(key.privateKey)
27
27
  }
28
28
 
29
29
  // Verify a token minted by this server. `audience` is checked because a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io-auth",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Authentication for mikser-io: an OAuth 2.1 authorization server (self-registering clients, authorization code + PKCE, refresh rotation) and HTTP Basic / JWT verifiers over Apache-format htpasswd and htgroup files in the working folder. Implements the ADR-0012 verifier contract, so it plugs in wherever a static token does — api, mcp, forms.",
5
5
  "main": "index.js",
6
6
  "type": "module",