@fabrika/auth 0.8.2 → 0.9.0-cloudflare.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabrika/auth",
3
- "version": "0.8.2",
3
+ "version": "0.9.0-cloudflare.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,12 +12,13 @@
12
12
  },
13
13
  "files": [
14
14
  "src",
15
- "!src/**/__tests__/**"
15
+ "!src/**/__tests__/**",
16
+ "types"
16
17
  ],
17
18
  "exports": {
18
19
  ".": {
19
20
  "bun": "./src/index.ts",
20
- "types": "./src/index.ts",
21
+ "types": "./types/src/index.d.ts",
21
22
  "default": "./src/index.ts"
22
23
  }
23
24
  },
@@ -27,8 +28,8 @@
27
28
  },
28
29
  "dependencies": {
29
30
  "zod": "^3.25.76",
30
- "@fabrika/auth-core": "0.8.2",
31
- "@fabrika/jwks": "0.8.2",
31
+ "@fabrika/auth-core": "0.9.0-cloudflare.2",
32
+ "@fabrika/jwks": "0.9.0-cloudflare.2",
32
33
  "jose": "^5.9.0"
33
34
  }
34
35
  }
package/src/app-schema.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  LOCALES,
8
8
  mailTemplateProblems,
9
9
  type MessageKind,
10
+ terminalIdentifyProblem,
10
11
  WORKLOAD_NAME,
11
12
  } from '@fabrika/auth-core'
12
13
  import { z } from 'zod'
@@ -230,6 +231,12 @@ const appSchemaShape = z.object({
230
231
  .optional(),
231
232
  members: z.object({ manage: z.string().min(1), list: z.string().min(1) }).optional(),
232
233
  directory: z.literal('labels').optional(),
234
+ terminal: z.object({
235
+ identify: z.string().superRefine((value, ctx) => {
236
+ const problem = terminalIdentifyProblem(value)
237
+ if (problem !== null) ctx.addIssue({ code: 'custom', message: `the terminal's identification path ${problem}` })
238
+ }),
239
+ }).strict().optional(),
233
240
  })
234
241
 
235
242
  /** One message's templates by language, each refused with the same rule the contract build applies. */
package/src/index.ts CHANGED
@@ -19,8 +19,8 @@ export type { AuthCarrier } from './types'
19
19
  // The HTTP transport for the SAME `IamRpc` contract, for a runtime with no Cloudflare service binding
20
20
  // (a long-running Bun/Node process). Drops into anywhere a binding goes — `createIam`, `IamClient`,
21
21
  // `env.IAM` — because the contract is unchanged and only the transport differs.
22
- export { DecodedIamMembership, HttpIamMembership, HttpIamRpc, IamTransportError } from './rpc-http'
23
- export type { IamMembershipMethod } from './rpc-http'
22
+ export { DecodedIamMembership, DecodedIamTerminals, HttpIamMembership, HttpIamRpc, HttpIamTerminals, IamTransportError } from './rpc-http'
23
+ export type { IamMembershipMethod, IamTerminalMethod } from './rpc-http'
24
24
  // The membership operations an application requests on its caller's behalf (scopes, members, share links).
25
25
  export { createIamMembership, IamMembership } from './membership'
26
26
  export type { IamMembershipEnv, MembershipRequestCaller } from './membership'
package/src/rpc-http.ts CHANGED
@@ -44,9 +44,13 @@
44
44
  import type {
45
45
  ApproveDeviceAuthorizationInput,
46
46
  ApproveDeviceAuthorizationResult,
47
+ AssertIdentityInput,
48
+ AssertIdentityResult,
47
49
  AssignMemberInput,
48
50
  AssignMemberResult,
49
51
  AuditInput,
52
+ CancelIdentificationInput,
53
+ CancelIdentificationResult,
50
54
  CreateScopeInput,
51
55
  CreateScopeResult,
52
56
  DenyDeviceAuthorizationResult,
@@ -54,8 +58,11 @@ import type {
54
58
  FindMemberResult,
55
59
  IamMembershipRpc,
56
60
  IamRpc,
61
+ IamTerminalRpc,
57
62
  InspectDeviceAuthorizationInput,
58
63
  InspectDeviceAuthorizationResult,
64
+ InspectIdentificationInput,
65
+ InspectIdentificationResult,
59
66
  IssueKeyInput,
60
67
  IssueKeyResult,
61
68
  IssueLinkInput,
@@ -82,6 +89,7 @@ import type {
82
89
  RevokeLinkResult,
83
90
  RevokeMemberInput,
84
91
  RevokeMemberResult,
92
+ TerminalFailure,
85
93
  } from '@fabrika/auth-core'
86
94
  import {
87
95
  parseApproveDeviceAuthorizationResult,
@@ -469,6 +477,62 @@ export class HttpIamMembership extends DecodedIamMembership {
469
477
  }
470
478
  }
471
479
 
480
+ /** The identification operations, by name, as a transport carries them. */
481
+ export type IamTerminalMethod = keyof IamTerminalRpc
482
+
483
+ const inspectIdentificationSuccessSchema = z.object({
484
+ ...okEnvelope,
485
+ terminal: z.object({ id: z.string().min(1), label: z.string().min(1) }),
486
+ expiresAt: z.number().finite(),
487
+ })
488
+ const assertIdentitySuccessSchema = z.object({ ...okEnvelope, completionUrl: z.string().url() })
489
+
490
+ /**
491
+ * `IamTerminalRpc` over any transport that answers each call with a JSON-shaped body: `HttpIamTerminals`, or a Workers
492
+ * binding to IAM's application entrypoint (ADR-0067). A body that breaks the contract is `IamTransportError`.
493
+ */
494
+ export class DecodedIamTerminals implements IamTerminalRpc {
495
+ readonly #call: (method: IamTerminalMethod, input: unknown) => Promise<unknown>
496
+
497
+ constructor(call: (method: IamTerminalMethod, input: unknown) => Promise<unknown>) {
498
+ this.#call = call
499
+ }
500
+
501
+ async inspectIdentification(input: InspectIdentificationInput): Promise<InspectIdentificationResult> {
502
+ const body = await this.#call('inspectIdentification', input)
503
+ return readTerminalFailure(body) ?? decode(inspectIdentificationSuccessSchema, body, 'inspectIdentification')
504
+ }
505
+
506
+ async assertIdentity(input: AssertIdentityInput): Promise<AssertIdentityResult> {
507
+ const body = await this.#call('assertIdentity', input)
508
+ return readTerminalFailure(body) ?? decode(assertIdentitySuccessSchema, body, 'assertIdentity')
509
+ }
510
+
511
+ async cancelIdentification(input: CancelIdentificationInput): Promise<CancelIdentificationResult> {
512
+ const body = await this.#call('cancelIdentification', input)
513
+ return readTerminalFailure(body) ?? decode(revokedSuccessSchema, body, 'cancelIdentification')
514
+ }
515
+ }
516
+
517
+ /**
518
+ * `IamTerminalRpc` over IAM's membership transport, `POST {origin}/membership/<method>`, with the application's own
519
+ * membership transport key, which pins the asserting application.
520
+ */
521
+ export class HttpIamTerminals extends DecodedIamTerminals {
522
+ constructor(options: HttpIamRpcOptions) {
523
+ const endpoint = transportEndpoint(options, '/membership/')
524
+ super((method, input) => callJson(endpoint, method, input))
525
+ }
526
+ }
527
+
528
+ const TERMINAL_REASONS = ['missing_token', 'invalid_token', 'not_allowed', 'not_found', 'invalid_principal'] as const
529
+
530
+ /** An identification refusal off the wire. An unknown reason degrades to `not_allowed`, never to an allow. */
531
+ function readTerminalFailure(body: unknown): TerminalFailure | null {
532
+ const reason = readFailure(body)
533
+ return reason === null ? null : { ok: false, reason: oneOf(reason, TERMINAL_REASONS, 'not_allowed') }
534
+ }
535
+
472
536
  /** The five delegation-path failure reasons `issueKey` / `revokeKey` / `listPrincipals` share. */
473
537
  const ISSUE_REASONS = ['missing_token', 'invalid_token', 'unknown_principal', 'disabled', 'not_allowed'] as const
474
538