@oatscenter/trellis 0.100.0-rc.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 +59 -0
- package/auth/authorization/client_context.d.ts +61 -0
- package/auth/authorization/client_context.js +266 -0
- package/auth/authorization/nats_registry.d.ts +41 -0
- package/auth/authorization/nats_registry.js +283 -0
- package/auth/authorization/provider_cache.d.ts +90 -0
- package/auth/authorization/provider_cache.js +783 -0
- package/auth/authorization/refresh.d.ts +46 -0
- package/auth/authorization/refresh.js +246 -0
- package/auth/authorization/types.d.ts +207 -0
- package/auth/authorization/types.js +60 -0
- package/auth/authorization_context.d.ts +11 -0
- package/auth/authorization_context.js +10 -0
- package/auth/browser/flow_types.d.ts +84 -0
- package/auth/browser/flow_types.js +70 -0
- package/auth/browser/portal.d.ts +21 -0
- package/auth/browser/portal.js +307 -0
- package/auth/browser/storage.d.ts +26 -0
- package/auth/browser/storage.js +224 -0
- package/auth/browser.d.ts +11 -0
- package/auth/browser.js +10 -0
- package/auth/browser_recovery.d.ts +13 -0
- package/auth/browser_recovery.js +38 -0
- package/auth/device_activation.d.ts +134 -0
- package/auth/device_activation.js +376 -0
- package/auth/http_error.d.ts +12 -0
- package/auth/http_error.js +32 -0
- package/auth/keys.d.ts +5 -0
- package/auth/keys.js +63 -0
- package/auth/mod.d.ts +16 -0
- package/auth/mod.js +16 -0
- package/auth/proof.d.ts +31 -0
- package/auth/proof.js +95 -0
- package/auth/protocol_wasm/trellis_protocol_wasm.d.ts +138 -0
- package/auth/protocol_wasm/trellis_protocol_wasm.js +605 -0
- package/auth/protocol_wasm/trellis_protocol_wasm_bg.wasm +0 -0
- package/auth/protocol_wasm/trellis_protocol_wasm_bytes.d.ts +1 -0
- package/auth/protocol_wasm/trellis_protocol_wasm_bytes.js +2 -0
- package/auth/protocol_wasm.d.ts +179 -0
- package/auth/protocol_wasm.js +133 -0
- package/auth/session_auth.d.ts +30 -0
- package/auth/session_auth.js +77 -0
- package/auth/session_proof.d.ts +53 -0
- package/auth/session_proof.js +43 -0
- package/auth/time.d.ts +6 -0
- package/auth/time.js +7 -0
- package/auth/trellis_id.d.ts +1 -0
- package/auth/trellis_id.js +5 -0
- package/auth/utils.d.ts +10 -0
- package/auth/utils.js +69 -0
- package/auth.d.ts +1 -0
- package/auth.js +1 -0
- package/caller.d.ts +111 -0
- package/caller.js +85 -0
- package/client.d.ts +14 -0
- package/client.js +1 -0
- package/client_connect.d.ts +143 -0
- package/client_connect.js +1097 -0
- package/codec.d.ts +13 -0
- package/codec.js +192 -0
- package/connection.d.ts +107 -0
- package/connection.js +396 -0
- package/device/activation.d.ts +42 -0
- package/device/activation.js +319 -0
- package/device.d.ts +80 -0
- package/device.js +687 -0
- package/env.d.ts +2 -0
- package/env.js +10 -0
- package/errors/AuthError.d.ts +30 -0
- package/errors/AuthError.js +34 -0
- package/errors/KVError.d.ts +30 -0
- package/errors/KVError.js +36 -0
- package/errors/OperationLifecycleError.d.ts +110 -0
- package/errors/OperationLifecycleError.js +142 -0
- package/errors/RemoteError.d.ts +61 -0
- package/errors/RemoteError.js +72 -0
- package/errors/SchemaValidationError.d.ts +48 -0
- package/errors/SchemaValidationError.js +49 -0
- package/errors/StoreError.d.ts +21 -0
- package/errors/StoreError.js +31 -0
- package/errors/TransferError.d.ts +21 -0
- package/errors/TransferError.js +31 -0
- package/errors/TransportError.d.ts +29 -0
- package/errors/TransportError.js +33 -0
- package/errors/TrellisError.d.ts +15 -0
- package/errors/TrellisError.js +15 -0
- package/errors/ValidationError.d.ts +50 -0
- package/errors/ValidationError.js +59 -0
- package/errors/index.d.ts +254 -0
- package/errors/index.js +238 -0
- package/generated.d.ts +4 -0
- package/generated.js +3 -0
- package/generated_support.d.ts +120 -0
- package/generated_support.js +359 -0
- package/globals.d.ts +9 -0
- package/globals.js +16 -0
- package/health_transport.d.ts +18 -0
- package/health_transport.js +32 -0
- package/helpers.d.ts +11 -0
- package/helpers.js +53 -0
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/internal_sdk/generated/apis/auth/mod.d.ts +805 -0
- package/internal_sdk/generated/apis/auth/mod.js +958 -0
- package/internal_sdk/generated/apis/core/mod.d.ts +79 -0
- package/internal_sdk/generated/apis/core/mod.js +93 -0
- package/internal_sdk/generated/apis/events/mod.d.ts +224 -0
- package/internal_sdk/generated/apis/events/mod.js +313 -0
- package/internal_sdk/generated/apis/health/mod.d.ts +106 -0
- package/internal_sdk/generated/apis/health/mod.js +132 -0
- package/internal_sdk/generated/apis/index.d.ts +7 -0
- package/internal_sdk/generated/apis/index.js +8 -0
- package/internal_sdk/generated/apis/jobs/mod.d.ts +176 -0
- package/internal_sdk/generated/apis/jobs/mod.js +209 -0
- package/internal_sdk/generated/apis/state/mod.d.ts +104 -0
- package/internal_sdk/generated/apis/state/mod.js +139 -0
- package/internal_sdk/generated/index.d.ts +4 -0
- package/internal_sdk/generated/index.js +5 -0
- package/internal_sdk/generated/package.json +18 -0
- package/internal_sdk/generated/participants/cli/mod.d.ts +402 -0
- package/internal_sdk/generated/participants/cli/mod.js +460 -0
- package/internal_sdk/generated/participants/cli/types.d.ts +2 -0
- package/internal_sdk/generated/participants/cli/types.js +3 -0
- package/internal_sdk/generated/participants/console/mod.d.ts +489 -0
- package/internal_sdk/generated/participants/console/mod.js +579 -0
- package/internal_sdk/generated/participants/console/types.d.ts +2 -0
- package/internal_sdk/generated/participants/console/types.js +3 -0
- package/internal_sdk/generated/participants/eventsRuntime/mod.d.ts +64 -0
- package/internal_sdk/generated/participants/eventsRuntime/mod.js +42 -0
- package/internal_sdk/generated/participants/eventsRuntime/types.d.ts +2 -0
- package/internal_sdk/generated/participants/eventsRuntime/types.js +3 -0
- package/internal_sdk/generated/participants/healthRuntime/mod.d.ts +58 -0
- package/internal_sdk/generated/participants/healthRuntime/mod.js +36 -0
- package/internal_sdk/generated/participants/healthRuntime/types.d.ts +2 -0
- package/internal_sdk/generated/participants/healthRuntime/types.js +3 -0
- package/internal_sdk/generated/participants/index.d.ts +8 -0
- package/internal_sdk/generated/participants/index.js +9 -0
- package/internal_sdk/generated/participants/jobsRuntime/mod.d.ts +64 -0
- package/internal_sdk/generated/participants/jobsRuntime/mod.js +42 -0
- package/internal_sdk/generated/participants/jobsRuntime/types.d.ts +2 -0
- package/internal_sdk/generated/participants/jobsRuntime/types.js +3 -0
- package/internal_sdk/generated/participants/platform/mod.d.ts +164 -0
- package/internal_sdk/generated/participants/platform/mod.js +149 -0
- package/internal_sdk/generated/participants/platform/types.d.ts +7 -0
- package/internal_sdk/generated/participants/platform/types.js +5 -0
- package/internal_sdk/generated/participants/portal/mod.d.ts +131 -0
- package/internal_sdk/generated/participants/portal/mod.js +109 -0
- package/internal_sdk/generated/participants/portal/types.d.ts +2 -0
- package/internal_sdk/generated/participants/portal/types.js +3 -0
- package/internal_sdk/generated/types/_internal/p0.d.ts +13511 -0
- package/internal_sdk/generated/types/_internal/p0.js +6105 -0
- package/internal_sdk/generated/types/index.d.ts +2951 -0
- package/internal_sdk/generated/types/index.js +1477 -0
- package/jobs.d.ts +309 -0
- package/jobs.js +389 -0
- package/kv.d.ts +85 -0
- package/kv.js +330 -0
- package/operations.d.ts +189 -0
- package/operations.js +847 -0
- package/package.json +73 -0
- package/pagination.d.ts +25 -0
- package/pagination.js +117 -0
- package/participant.d.ts +6 -0
- package/participant.js +6 -0
- package/participant_runtime/api.d.ts +131 -0
- package/participant_runtime/api.js +42 -0
- package/participant_runtime/json.d.ts +16 -0
- package/participant_runtime/json.js +193 -0
- package/participant_runtime/metadata.d.ts +34 -0
- package/participant_runtime/metadata.js +4 -0
- package/participant_runtime/participant.d.ts +200 -0
- package/participant_runtime/participant.js +330 -0
- package/participant_runtime/schemas.d.ts +347 -0
- package/participant_runtime/schemas.js +211 -0
- package/participant_runtime/surface_names.d.ts +16 -0
- package/participant_runtime/surface_names.js +34 -0
- package/portable.d.ts +30 -0
- package/portable.js +14 -0
- package/provider.d.ts +69 -0
- package/provider.js +68 -0
- package/runtime_transport.d.ts +25 -0
- package/runtime_transport.js +48 -0
- package/service/mod.d.ts +14 -0
- package/service/mod.js +13 -0
- package/service/outbox_inbox.d.ts +259 -0
- package/service/outbox_inbox.js +908 -0
- package/service/runtime/bootstrap.d.ts +56 -0
- package/service/runtime/bootstrap.js +226 -0
- package/service/runtime/core.d.ts +46 -0
- package/service/runtime/core.js +1747 -0
- package/service/runtime/health.d.ts +97 -0
- package/service/runtime/health.js +153 -0
- package/service/runtime/health_schemas.d.ts +23 -0
- package/service/runtime/health_schemas.js +20 -0
- package/service/runtime/internal_jobs/active-job.d.ts +40 -0
- package/service/runtime/internal_jobs/active-job.js +91 -0
- package/service/runtime/internal_jobs/bindings.d.ts +40 -0
- package/service/runtime/internal_jobs/bindings.js +1 -0
- package/service/runtime/internal_jobs/cancellation-registry.d.ts +13 -0
- package/service/runtime/internal_jobs/cancellation-registry.js +55 -0
- package/service/runtime/internal_jobs/heartbeat.d.ts +33 -0
- package/service/runtime/internal_jobs/heartbeat.js +65 -0
- package/service/runtime/internal_jobs/job-manager.d.ts +126 -0
- package/service/runtime/internal_jobs/job-manager.js +821 -0
- package/service/runtime/internal_jobs/key-coordinator.d.ts +280 -0
- package/service/runtime/internal_jobs/key-coordinator.js +747 -0
- package/service/runtime/internal_jobs/projection.d.ts +3 -0
- package/service/runtime/internal_jobs/projection.js +42 -0
- package/service/runtime/internal_jobs/runtime-worker.d.ts +126 -0
- package/service/runtime/internal_jobs/runtime-worker.js +454 -0
- package/service/runtime/internal_jobs/types.d.ts +246 -0
- package/service/runtime/internal_jobs/types.js +163 -0
- package/service/runtime/logger.d.ts +2 -0
- package/service/runtime/logger.js +7 -0
- package/service/runtime/runtime.d.ts +16 -0
- package/service/runtime/runtime.js +1 -0
- package/service/runtime/service.d.ts +372 -0
- package/service/runtime/service.js +1896 -0
- package/service/runtime/transfer/download.d.ts +27 -0
- package/service/runtime/transfer/download.js +1 -0
- package/service/runtime/transfer/protocol.d.ts +15 -0
- package/service/runtime/transfer/protocol.js +46 -0
- package/service/runtime/transfer/queue.d.ts +18 -0
- package/service/runtime/transfer/queue.js +136 -0
- package/service/runtime/transfer/upload.d.ts +40 -0
- package/service/runtime/transfer/upload.js +27 -0
- package/service/runtime/transfer.d.ts +62 -0
- package/service/runtime/transfer.js +763 -0
- package/session.d.ts +862 -0
- package/session.js +3407 -0
- package/store.d.ts +71 -0
- package/store.js +412 -0
- package/store_entry.d.ts +20 -0
- package/store_entry.js +75 -0
- package/tasks.d.ts +10 -0
- package/tasks.js +28 -0
- package/telemetry/carrier.d.ts +8 -0
- package/telemetry/carrier.js +20 -0
- package/telemetry/core.d.ts +7 -0
- package/telemetry/core.js +14 -0
- package/telemetry/env.d.ts +2 -0
- package/telemetry/env.js +10 -0
- package/telemetry/init.d.ts +2 -0
- package/telemetry/init.js +7 -0
- package/telemetry/metrics.d.ts +81 -0
- package/telemetry/metrics.js +267 -0
- package/telemetry/mod.d.ts +13 -0
- package/telemetry/mod.js +8 -0
- package/telemetry/nats.d.ts +6 -0
- package/telemetry/nats.js +6 -0
- package/telemetry/result.d.ts +1 -0
- package/telemetry/result.js +18 -0
- package/telemetry/runtime.d.ts +1 -0
- package/telemetry/runtime.js +141 -0
- package/telemetry/trace.d.ts +4 -0
- package/telemetry/trace.js +26 -0
- package/telemetry/trellis.d.ts +1 -0
- package/telemetry/trellis.js +1 -0
- package/telemetry.d.ts +1 -0
- package/telemetry.js +1 -0
- package/transfer.d.ts +112 -0
- package/transfer.js +525 -0
- package/transfer_protocol.d.ts +1 -0
- package/transfer_protocol.js +13 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { machineErrorCode } from "../errors/index.js";
|
|
2
|
+
/** Classifies an exact auth machine error code. */
|
|
3
|
+
export function classifyBrowserAuthError(error) {
|
|
4
|
+
const code = machineErrorCode(error);
|
|
5
|
+
const result = (kind, recoverable) => ({
|
|
6
|
+
kind,
|
|
7
|
+
recoverable,
|
|
8
|
+
...(code ? { code, reason: code } : {}),
|
|
9
|
+
});
|
|
10
|
+
switch (code) {
|
|
11
|
+
case "approval_denied":
|
|
12
|
+
case "invalid_credentials":
|
|
13
|
+
case "user_inactive":
|
|
14
|
+
case "inactive_account":
|
|
15
|
+
return result("policy_denied", false);
|
|
16
|
+
case "insufficient_capabilities":
|
|
17
|
+
return result("insufficient_capabilities", false);
|
|
18
|
+
case "not_ready":
|
|
19
|
+
case "runtime_unavailable":
|
|
20
|
+
return result("runtime_unavailable", false);
|
|
21
|
+
case "flow_expired":
|
|
22
|
+
case "flow_not_found":
|
|
23
|
+
return result("recoverable_expired_flow", true);
|
|
24
|
+
case "session_not_found":
|
|
25
|
+
case "session_expired":
|
|
26
|
+
case "user_not_found":
|
|
27
|
+
case "contract_not_active":
|
|
28
|
+
return result("recoverable_stale_session", true);
|
|
29
|
+
case "auth_required":
|
|
30
|
+
return result("recoverable_auth_required", true);
|
|
31
|
+
default:
|
|
32
|
+
return result("unknown", false);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Returns whether a browser auth error can silently restart sign-in. */
|
|
36
|
+
export function isRecoverableBrowserAuthError(error) {
|
|
37
|
+
return classifyBrowserAuthError(error).recoverable;
|
|
38
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { BaseError } from "@oatscenter/result";
|
|
2
|
+
import type { AsyncResult } from "@oatscenter/result";
|
|
3
|
+
import type { StaticDecode } from "typebox";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import type { API as AUTH_API, DeviceUserAuthoritiesListInput, DeviceUserAuthoritiesListOutput, DeviceUserAuthoritiesResolveInput, DeviceUserAuthoritiesResolveOutput, DeviceUserAuthoritiesResolveProgress, DeviceUserAuthoritiesRevokeInput, DeviceUserAuthoritiesRevokeOutput } from "../internal_sdk/generated/apis/auth/mod.js";
|
|
6
|
+
import type { OperationRef } from "../operations.ts";
|
|
7
|
+
import type { GeneratedParticipant } from "../participant_runtime/participant.ts";
|
|
8
|
+
import { createAuth } from "./session_auth.ts";
|
|
9
|
+
export declare const DeviceActivationPayloadSchema: Type.TObject<{
|
|
10
|
+
v: Type.TLiteral<1>;
|
|
11
|
+
publicIdentityKey: Type.TString;
|
|
12
|
+
nonce: Type.TString;
|
|
13
|
+
qrMac: Type.TString;
|
|
14
|
+
}>;
|
|
15
|
+
export type DeviceActivationPayload = StaticDecode<typeof DeviceActivationPayloadSchema>;
|
|
16
|
+
export type AuthResolveDeviceUserAuthoritiesInput = DeviceUserAuthoritiesResolveInput;
|
|
17
|
+
export type AuthResolveDeviceUserAuthoritiesProgress = DeviceUserAuthoritiesResolveProgress;
|
|
18
|
+
export type AuthResolveDeviceUserAuthoritiesOutput = DeviceUserAuthoritiesResolveOutput;
|
|
19
|
+
export type AuthDeviceUserAuthoritiesListInput = DeviceUserAuthoritiesListInput;
|
|
20
|
+
export type AuthDeviceUserAuthoritiesListOutput = DeviceUserAuthoritiesListOutput;
|
|
21
|
+
export type AuthDeviceUserAuthoritiesRevokeInput = DeviceUserAuthoritiesRevokeInput;
|
|
22
|
+
export type AuthDeviceUserAuthoritiesRevokeResponse = DeviceUserAuthoritiesRevokeOutput;
|
|
23
|
+
export type DeviceIdentity = {
|
|
24
|
+
identitySeed: Uint8Array;
|
|
25
|
+
identitySeedBase64url: string;
|
|
26
|
+
publicIdentityKey: string;
|
|
27
|
+
activationKey: Uint8Array;
|
|
28
|
+
activationKeyBase64url: string;
|
|
29
|
+
};
|
|
30
|
+
/** Device-local credential for one exact origin-bound user companion. */
|
|
31
|
+
export type DeviceCompanionIdentity = {
|
|
32
|
+
installationSeed: Uint8Array;
|
|
33
|
+
installationSeedBase64url: string;
|
|
34
|
+
installationPublicKey: string;
|
|
35
|
+
};
|
|
36
|
+
type AuthResolveDeviceUserAuthoritiesOperationShape = {
|
|
37
|
+
subject: string;
|
|
38
|
+
input: typeof AUTH_API.actions["operation:DeviceUserAuthorities.Resolve"]["input"];
|
|
39
|
+
progress: typeof AUTH_API.actions["operation:DeviceUserAuthorities.Resolve"]["progress"];
|
|
40
|
+
output: typeof AUTH_API.actions["operation:DeviceUserAuthorities.Resolve"]["output"];
|
|
41
|
+
};
|
|
42
|
+
export type AuthResolveDeviceUserAuthoritiesOperation = OperationRef<AuthResolveDeviceUserAuthoritiesOperationShape, AuthResolveDeviceUserAuthoritiesProgress, AuthResolveDeviceUserAuthoritiesOutput>;
|
|
43
|
+
export type DeviceActivationTransport = {
|
|
44
|
+
authDeviceUserAuthoritiesResolve(input: AuthResolveDeviceUserAuthoritiesInput): {
|
|
45
|
+
start(): AsyncResult<AuthResolveDeviceUserAuthoritiesOperation, BaseError>;
|
|
46
|
+
};
|
|
47
|
+
authDeviceUserAuthoritiesList(input: AuthDeviceUserAuthoritiesListInput): AsyncResult<AuthDeviceUserAuthoritiesListOutput, BaseError>;
|
|
48
|
+
authDeviceUserAuthoritiesRevoke(input: AuthDeviceUserAuthoritiesRevokeInput): AsyncResult<AuthDeviceUserAuthoritiesRevokeResponse, BaseError>;
|
|
49
|
+
};
|
|
50
|
+
export declare function deriveDeviceIdentity(deviceRootSecret: Uint8Array): Promise<DeviceIdentity>;
|
|
51
|
+
/** Derive the installation credential for one exact child participant. */
|
|
52
|
+
export declare function deriveDeviceUserCompanion(deviceRootSecret: Uint8Array, trellisUrl: string, childParticipantId: string): Promise<DeviceCompanionIdentity>;
|
|
53
|
+
export declare function deriveDeviceQrMac(input: {
|
|
54
|
+
activationKey: Uint8Array | string;
|
|
55
|
+
publicIdentityKey: string;
|
|
56
|
+
nonce: string;
|
|
57
|
+
}): Promise<string>;
|
|
58
|
+
export declare function buildDeviceActivationPayload(input: {
|
|
59
|
+
activationKey: Uint8Array | string;
|
|
60
|
+
publicIdentityKey: string;
|
|
61
|
+
nonce: string;
|
|
62
|
+
}): Promise<DeviceActivationPayload>;
|
|
63
|
+
export declare function encodeDeviceActivationPayload(payload: DeviceActivationPayload): string;
|
|
64
|
+
export declare function parseDeviceActivationPayload(value: string): DeviceActivationPayload;
|
|
65
|
+
export declare function deriveDeviceConfirmationCode(input: {
|
|
66
|
+
activationKey: Uint8Array | string;
|
|
67
|
+
publicIdentityKey: string;
|
|
68
|
+
nonce: string;
|
|
69
|
+
}): Promise<string>;
|
|
70
|
+
export declare function verifyDeviceConfirmationCode(input: {
|
|
71
|
+
activationKey: Uint8Array | string;
|
|
72
|
+
publicIdentityKey: string;
|
|
73
|
+
nonce: string;
|
|
74
|
+
confirmationCode: string;
|
|
75
|
+
}): Promise<boolean>;
|
|
76
|
+
/** Submit one proof-bound device enrollment request. */
|
|
77
|
+
export declare function requestDeviceEnrollment(args: {
|
|
78
|
+
trellisUrl: string;
|
|
79
|
+
publicIdentityKey: string;
|
|
80
|
+
identitySeed: Uint8Array | string;
|
|
81
|
+
sessionIdentity: Awaited<ReturnType<typeof createAuth>>;
|
|
82
|
+
connectionId: string;
|
|
83
|
+
participantId: string;
|
|
84
|
+
packageEvidence: GeneratedParticipant["packageEvidence"];
|
|
85
|
+
participantPath: string;
|
|
86
|
+
packageDigest: string;
|
|
87
|
+
challengeDigest: string;
|
|
88
|
+
confirmationCode: string;
|
|
89
|
+
companion?: {
|
|
90
|
+
participantId: string;
|
|
91
|
+
kind: "app" | "agent";
|
|
92
|
+
installation: DeviceCompanionIdentity;
|
|
93
|
+
};
|
|
94
|
+
provisioningSecret?: string;
|
|
95
|
+
signal?: AbortSignal;
|
|
96
|
+
}): Promise<Record<string, unknown>>;
|
|
97
|
+
/**
|
|
98
|
+
* Retry the proof-bound device bootstrap operation until activation completes.
|
|
99
|
+
*
|
|
100
|
+
* Each attempt re-signs the same bootstrap request; the server returns
|
|
101
|
+
* `activation_pending` with a suggested `retryAfterMs` until the device is
|
|
102
|
+
* approved, then `ready`.
|
|
103
|
+
*/
|
|
104
|
+
export declare function waitForDeviceActivation(args: {
|
|
105
|
+
trellisUrl: string;
|
|
106
|
+
publicIdentityKey: string;
|
|
107
|
+
identitySeed: Uint8Array | string;
|
|
108
|
+
activationKey: Uint8Array | string;
|
|
109
|
+
participantId: string;
|
|
110
|
+
packageEvidence: GeneratedParticipant["packageEvidence"];
|
|
111
|
+
participantPath: string;
|
|
112
|
+
packageDigest: string;
|
|
113
|
+
provisioningSecret?: string;
|
|
114
|
+
nonce?: string;
|
|
115
|
+
signal?: AbortSignal;
|
|
116
|
+
pollIntervalMs?: number;
|
|
117
|
+
sessionIdentity?: Awaited<ReturnType<typeof createAuth>>;
|
|
118
|
+
connectionId?: string;
|
|
119
|
+
companion?: {
|
|
120
|
+
participantId: string;
|
|
121
|
+
kind: "app" | "agent";
|
|
122
|
+
installation: DeviceCompanionIdentity;
|
|
123
|
+
};
|
|
124
|
+
}): Promise<{
|
|
125
|
+
state: "ready";
|
|
126
|
+
sessionIdentity: Awaited<ReturnType<typeof createAuth>>;
|
|
127
|
+
bundle: Record<string, unknown>;
|
|
128
|
+
}>;
|
|
129
|
+
export declare function createDeviceActivationClient(client: DeviceActivationTransport): {
|
|
130
|
+
resolveDeviceUserAuthorities(input: AuthResolveDeviceUserAuthoritiesInput): Promise<AuthResolveDeviceUserAuthoritiesOperation>;
|
|
131
|
+
listDeviceActivations(input: AuthDeviceUserAuthoritiesListInput): Promise<import("../internal_sdk/generated/types/_internal/p0").AuthDeviceUserAuthoritiesListResponse>;
|
|
132
|
+
revokeDeviceActivation(input: AuthDeviceUserAuthoritiesRevokeInput): Promise<import("../internal_sdk/generated/types/_internal/p0").AuthDeviceUserAuthoritiesRevokeResponse>;
|
|
133
|
+
};
|
|
134
|
+
export {};
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { Value } from "typebox/value";
|
|
3
|
+
import { ulid } from "ulid";
|
|
4
|
+
import { decodeTrellisHttpError } from "./http_error.js";
|
|
5
|
+
import { importEd25519PrivateKeyFromSeedBase64url, publicKeyBase64urlFromPrivateKey, } from "./keys.js";
|
|
6
|
+
import { createAuth } from "./session_auth.js";
|
|
7
|
+
import { SESSION_PROOF_FORMAT_V1, sessionProofRequestDigest, } from "./session_proof.js";
|
|
8
|
+
import { base64urlDecode, base64urlEncode, canonicalizeJsonValue, sha256, toArrayBuffer, utf8, } from "./utils.js";
|
|
9
|
+
const DEVICE_IDENTITY_HKDF_INFO = "trellis/device-identity/v1";
|
|
10
|
+
const DEVICE_ACTIVATION_HKDF_INFO = "trellis/device-activate/v1";
|
|
11
|
+
const DEVICE_USER_COMPANION_DOMAIN = "trellis.device.user-companion.v1";
|
|
12
|
+
const DEVICE_QR_MAC_DOMAIN = "trellis-device-qr/v1";
|
|
13
|
+
const DEVICE_CONFIRMATION_DOMAIN = "trellis-device-confirm/v1";
|
|
14
|
+
const CROCKFORD_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
15
|
+
const DEFAULT_WAIT_POLL_INTERVAL_MS = 3_000;
|
|
16
|
+
export const DeviceActivationPayloadSchema = Type.Object({
|
|
17
|
+
v: Type.Literal(1),
|
|
18
|
+
publicIdentityKey: Type.String({ minLength: 1 }),
|
|
19
|
+
nonce: Type.String({ minLength: 1 }),
|
|
20
|
+
qrMac: Type.String({ minLength: 1 }),
|
|
21
|
+
});
|
|
22
|
+
function concatBytes(parts) {
|
|
23
|
+
const size = parts.reduce((total, part) => total + part.length, 0);
|
|
24
|
+
const bytes = new Uint8Array(size);
|
|
25
|
+
let offset = 0;
|
|
26
|
+
for (const part of parts) {
|
|
27
|
+
bytes.set(part, offset);
|
|
28
|
+
offset += part.length;
|
|
29
|
+
}
|
|
30
|
+
return bytes;
|
|
31
|
+
}
|
|
32
|
+
function normalizeSecretBytes(value, name) {
|
|
33
|
+
if (typeof value === "string") {
|
|
34
|
+
const decoded = base64urlDecode(value);
|
|
35
|
+
if (decoded.length === 0)
|
|
36
|
+
throw new Error(`${name} must not be empty`);
|
|
37
|
+
return decoded;
|
|
38
|
+
}
|
|
39
|
+
if (value.length === 0)
|
|
40
|
+
throw new Error(`${name} must not be empty`);
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
async function hkdfSha256(inputKeyingMaterial, info, length) {
|
|
44
|
+
const key = await crypto.subtle.importKey("raw", toArrayBuffer(inputKeyingMaterial), "HKDF", false, ["deriveBits"]);
|
|
45
|
+
const derivedBits = await crypto.subtle.deriveBits({
|
|
46
|
+
name: "HKDF",
|
|
47
|
+
hash: "SHA-256",
|
|
48
|
+
salt: toArrayBuffer(new Uint8Array(0)),
|
|
49
|
+
info: toArrayBuffer(typeof info === "string" ? utf8(info) : info),
|
|
50
|
+
}, key, length * 8);
|
|
51
|
+
return new Uint8Array(derivedBits);
|
|
52
|
+
}
|
|
53
|
+
async function hmacSha256(keyBytes, data) {
|
|
54
|
+
const key = await crypto.subtle.importKey("raw", toArrayBuffer(keyBytes), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
55
|
+
return new Uint8Array(await crypto.subtle.sign("HMAC", key, toArrayBuffer(data)));
|
|
56
|
+
}
|
|
57
|
+
function crockfordEncode(bytes) {
|
|
58
|
+
let value = 0;
|
|
59
|
+
let bits = 0;
|
|
60
|
+
let output = "";
|
|
61
|
+
for (const byte of bytes) {
|
|
62
|
+
value = (value << 8) | byte;
|
|
63
|
+
bits += 8;
|
|
64
|
+
while (bits >= 5) {
|
|
65
|
+
bits -= 5;
|
|
66
|
+
output += CROCKFORD_ALPHABET[(value >>> bits) & 31] ?? "0";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (bits > 0) {
|
|
70
|
+
output += CROCKFORD_ALPHABET[(value << (5 - bits)) & 31] ?? "0";
|
|
71
|
+
}
|
|
72
|
+
return output;
|
|
73
|
+
}
|
|
74
|
+
function normalizeCrockford(value) {
|
|
75
|
+
return value.trim().toUpperCase().replace(/O/g, "0").replace(/[IL]/g, "1");
|
|
76
|
+
}
|
|
77
|
+
async function sleep(ms, signal) {
|
|
78
|
+
if (signal?.aborted) {
|
|
79
|
+
throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
80
|
+
}
|
|
81
|
+
await new Promise((resolve, reject) => {
|
|
82
|
+
const timer = setTimeout(() => {
|
|
83
|
+
signal?.removeEventListener("abort", onAbort);
|
|
84
|
+
resolve();
|
|
85
|
+
}, ms);
|
|
86
|
+
function onAbort() {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
reject(signal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
89
|
+
}
|
|
90
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export async function deriveDeviceIdentity(deviceRootSecret) {
|
|
94
|
+
if (deviceRootSecret.length !== 32) {
|
|
95
|
+
throw new Error(`Invalid device root secret length: ${deviceRootSecret.length} (expected 32)`);
|
|
96
|
+
}
|
|
97
|
+
const identitySeed = await hkdfSha256(deviceRootSecret, DEVICE_IDENTITY_HKDF_INFO, 32);
|
|
98
|
+
const activationKey = await hkdfSha256(deviceRootSecret, DEVICE_ACTIVATION_HKDF_INFO, 32);
|
|
99
|
+
const identitySeedBase64url = base64urlEncode(identitySeed);
|
|
100
|
+
const identityPrivateKey = await importEd25519PrivateKeyFromSeedBase64url(identitySeedBase64url);
|
|
101
|
+
const publicIdentityKey = await publicKeyBase64urlFromPrivateKey(identityPrivateKey);
|
|
102
|
+
return {
|
|
103
|
+
identitySeed,
|
|
104
|
+
identitySeedBase64url,
|
|
105
|
+
publicIdentityKey,
|
|
106
|
+
activationKey,
|
|
107
|
+
activationKeyBase64url: base64urlEncode(activationKey),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Derive the installation credential for one exact child participant. */
|
|
111
|
+
export async function deriveDeviceUserCompanion(deviceRootSecret, trellisUrl, childParticipantId) {
|
|
112
|
+
if (deviceRootSecret.length !== 32) {
|
|
113
|
+
throw new Error(`Invalid device root secret length: ${deviceRootSecret.length} (expected 32)`);
|
|
114
|
+
}
|
|
115
|
+
if (!childParticipantId) {
|
|
116
|
+
throw new Error("Companion participant ID must not be empty");
|
|
117
|
+
}
|
|
118
|
+
const url = new URL(trellisUrl);
|
|
119
|
+
const loopback = url.hostname === "localhost" || url.hostname === "[::1]" ||
|
|
120
|
+
url.hostname.startsWith("127.");
|
|
121
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
122
|
+
throw new Error("Trellis URL must use HTTPS except on loopback");
|
|
123
|
+
}
|
|
124
|
+
const origin = url.origin;
|
|
125
|
+
const encoder = new TextEncoder();
|
|
126
|
+
const parts = [
|
|
127
|
+
encoder.encode(DEVICE_USER_COMPANION_DOMAIN),
|
|
128
|
+
encoder.encode(origin),
|
|
129
|
+
encoder.encode(childParticipantId),
|
|
130
|
+
];
|
|
131
|
+
const info = concatBytes(parts.flatMap((part) => {
|
|
132
|
+
const length = new Uint8Array(8);
|
|
133
|
+
new DataView(length.buffer).setBigUint64(0, BigInt(part.length));
|
|
134
|
+
return [length, part];
|
|
135
|
+
}));
|
|
136
|
+
const installationSeed = await hkdfSha256(deviceRootSecret, info, 32);
|
|
137
|
+
const installationAuth = await createAuth({
|
|
138
|
+
sessionKeySeed: base64urlEncode(installationSeed),
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
installationSeed,
|
|
142
|
+
installationSeedBase64url: base64urlEncode(installationSeed),
|
|
143
|
+
installationPublicKey: installationAuth.sessionKey,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export async function deriveDeviceQrMac(input) {
|
|
147
|
+
const activationKey = normalizeSecretBytes(input.activationKey, "activationKey");
|
|
148
|
+
const mac = await hmacSha256(activationKey, concatBytes([
|
|
149
|
+
utf8(DEVICE_QR_MAC_DOMAIN),
|
|
150
|
+
utf8(input.publicIdentityKey),
|
|
151
|
+
utf8(input.nonce),
|
|
152
|
+
]));
|
|
153
|
+
return base64urlEncode(mac.slice(0, 8));
|
|
154
|
+
}
|
|
155
|
+
export async function buildDeviceActivationPayload(input) {
|
|
156
|
+
const qrMac = await deriveDeviceQrMac(input);
|
|
157
|
+
return {
|
|
158
|
+
v: 1,
|
|
159
|
+
publicIdentityKey: input.publicIdentityKey,
|
|
160
|
+
nonce: input.nonce,
|
|
161
|
+
qrMac,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export function encodeDeviceActivationPayload(payload) {
|
|
165
|
+
return base64urlEncode(utf8(JSON.stringify(payload)));
|
|
166
|
+
}
|
|
167
|
+
export function parseDeviceActivationPayload(value) {
|
|
168
|
+
const decoded = new TextDecoder().decode(base64urlDecode(value));
|
|
169
|
+
const parsed = JSON.parse(decoded);
|
|
170
|
+
if (!Value.Check(DeviceActivationPayloadSchema, parsed)) {
|
|
171
|
+
throw new Error("Invalid device activation payload");
|
|
172
|
+
}
|
|
173
|
+
return parsed;
|
|
174
|
+
}
|
|
175
|
+
export async function deriveDeviceConfirmationCode(input) {
|
|
176
|
+
const activationKey = normalizeSecretBytes(input.activationKey, "activationKey");
|
|
177
|
+
const mac = await hmacSha256(activationKey, concatBytes([
|
|
178
|
+
utf8(DEVICE_CONFIRMATION_DOMAIN),
|
|
179
|
+
utf8(input.publicIdentityKey),
|
|
180
|
+
utf8(input.nonce),
|
|
181
|
+
]));
|
|
182
|
+
return crockfordEncode(mac.slice(0, 5)).slice(0, 8);
|
|
183
|
+
}
|
|
184
|
+
export async function verifyDeviceConfirmationCode(input) {
|
|
185
|
+
const expected = await deriveDeviceConfirmationCode(input);
|
|
186
|
+
return normalizeCrockford(expected) ===
|
|
187
|
+
normalizeCrockford(input.confirmationCode);
|
|
188
|
+
}
|
|
189
|
+
/** Submit one proof-bound device enrollment request. */
|
|
190
|
+
export async function requestDeviceEnrollment(args) {
|
|
191
|
+
const identityAuth = await createAuth({
|
|
192
|
+
sessionKeySeed: base64urlEncode(normalizeSecretBytes(args.identitySeed, "identitySeed")),
|
|
193
|
+
});
|
|
194
|
+
const unsignedRequest = {
|
|
195
|
+
identityKeyId: base64urlEncode(await sha256(base64urlDecode(identityAuth.sessionKey))),
|
|
196
|
+
identityPublicKey: args.publicIdentityKey,
|
|
197
|
+
sessionKey: args.sessionIdentity.sessionKey,
|
|
198
|
+
connectionId: args.connectionId,
|
|
199
|
+
requestId: ulid(),
|
|
200
|
+
iat: Date.now(),
|
|
201
|
+
participantId: args.participantId,
|
|
202
|
+
packageEvidence: args.packageEvidence,
|
|
203
|
+
participantPath: args.participantPath,
|
|
204
|
+
packageDigest: args.packageDigest,
|
|
205
|
+
challengeDigest: args.challengeDigest,
|
|
206
|
+
confirmationCode: args.confirmationCode,
|
|
207
|
+
...(args.provisioningSecret === undefined ? {} : {
|
|
208
|
+
provisioningSecret: args.provisioningSecret,
|
|
209
|
+
}),
|
|
210
|
+
};
|
|
211
|
+
if (args.companion) {
|
|
212
|
+
const claim = {
|
|
213
|
+
participantId: args.companion.participantId,
|
|
214
|
+
kind: args.companion.kind,
|
|
215
|
+
installationPublicKey: args.companion.installation.installationPublicKey,
|
|
216
|
+
};
|
|
217
|
+
const requestDigest = base64urlEncode(await sha256(utf8(canonicalizeJsonValue({
|
|
218
|
+
format: DEVICE_USER_COMPANION_DOMAIN,
|
|
219
|
+
origin: new URL(args.trellisUrl).origin,
|
|
220
|
+
enrollment: unsignedRequest,
|
|
221
|
+
claim,
|
|
222
|
+
}))));
|
|
223
|
+
const installationAuth = await createAuth({
|
|
224
|
+
sessionKeySeed: args.companion.installation.installationSeedBase64url,
|
|
225
|
+
});
|
|
226
|
+
unsignedRequest.companion = {
|
|
227
|
+
...claim,
|
|
228
|
+
requestProof: base64urlEncode(await installationAuth.sign(base64urlDecode(requestDigest))),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const response = await fetch(new URL("/auth/device/enroll", args.trellisUrl), {
|
|
232
|
+
method: "POST",
|
|
233
|
+
signal: args.signal,
|
|
234
|
+
headers: { "Content-Type": "application/json" },
|
|
235
|
+
body: JSON.stringify({
|
|
236
|
+
...unsignedRequest,
|
|
237
|
+
proof: await identityAuth.signSessionProof({
|
|
238
|
+
purpose: "deviceEnrollment",
|
|
239
|
+
origin: new URL(args.trellisUrl).origin,
|
|
240
|
+
unsignedRequest,
|
|
241
|
+
}),
|
|
242
|
+
}),
|
|
243
|
+
});
|
|
244
|
+
if (!response.ok)
|
|
245
|
+
throw await decodeTrellisHttpError(response);
|
|
246
|
+
const body = await response.json();
|
|
247
|
+
if (typeof body !== "object" || body === null) {
|
|
248
|
+
throw new Error("Invalid device enrollment response");
|
|
249
|
+
}
|
|
250
|
+
return body;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Retry the proof-bound device bootstrap operation until activation completes.
|
|
254
|
+
*
|
|
255
|
+
* Each attempt re-signs the same bootstrap request; the server returns
|
|
256
|
+
* `activation_pending` with a suggested `retryAfterMs` until the device is
|
|
257
|
+
* approved, then `ready`.
|
|
258
|
+
*/
|
|
259
|
+
export async function waitForDeviceActivation(args) {
|
|
260
|
+
const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_WAIT_POLL_INTERVAL_MS;
|
|
261
|
+
const nonce = args.nonce ??
|
|
262
|
+
base64urlEncode(crypto.getRandomValues(new Uint8Array(32)));
|
|
263
|
+
const identitySeed = normalizeSecretBytes(args.identitySeed, "identitySeed");
|
|
264
|
+
const sessionIdentity = args.sessionIdentity ?? await createAuth({
|
|
265
|
+
sessionKeySeed: base64urlEncode(crypto.getRandomValues(new Uint8Array(32))),
|
|
266
|
+
});
|
|
267
|
+
const identityAuth = await createAuth({
|
|
268
|
+
sessionKeySeed: base64urlEncode(identitySeed),
|
|
269
|
+
});
|
|
270
|
+
const identityKeyId = base64urlEncode(await sha256(base64urlDecode(identityAuth.sessionKey)));
|
|
271
|
+
const challengeDigest = base64urlEncode(await sha256(utf8(nonce)));
|
|
272
|
+
const confirmationCode = await deriveDeviceConfirmationCode({
|
|
273
|
+
activationKey: args.activationKey,
|
|
274
|
+
publicIdentityKey: args.publicIdentityKey,
|
|
275
|
+
nonce,
|
|
276
|
+
});
|
|
277
|
+
const connectionId = args.connectionId ?? ulid();
|
|
278
|
+
let reviewId;
|
|
279
|
+
let reviewDeadline;
|
|
280
|
+
while (true) {
|
|
281
|
+
if (reviewDeadline !== undefined && performance.now() >= reviewDeadline) {
|
|
282
|
+
throw new Error("device activation review expired");
|
|
283
|
+
}
|
|
284
|
+
let body;
|
|
285
|
+
try {
|
|
286
|
+
body = await requestDeviceEnrollment({
|
|
287
|
+
...args,
|
|
288
|
+
identitySeed,
|
|
289
|
+
sessionIdentity,
|
|
290
|
+
connectionId,
|
|
291
|
+
challengeDigest,
|
|
292
|
+
confirmationCode,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
if (args.signal?.aborted) {
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
if (!(error instanceof TypeError)) {
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
await sleep(pollIntervalMs, args.signal);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const state = Reflect.get(body, "state");
|
|
306
|
+
if (state === "approved") {
|
|
307
|
+
const bootstrapRequest = {
|
|
308
|
+
identityKeyId,
|
|
309
|
+
sessionKey: sessionIdentity.sessionKey,
|
|
310
|
+
connectionId,
|
|
311
|
+
requestId: ulid(),
|
|
312
|
+
iat: Date.now(),
|
|
313
|
+
packageEvidence: args.packageEvidence,
|
|
314
|
+
participantPath: args.participantPath,
|
|
315
|
+
packageDigest: args.packageDigest,
|
|
316
|
+
};
|
|
317
|
+
const bootstrapResponse = await fetch(new URL("/bootstrap/device", args.trellisUrl), {
|
|
318
|
+
method: "POST",
|
|
319
|
+
signal: args.signal,
|
|
320
|
+
headers: { "Content-Type": "application/json" },
|
|
321
|
+
body: JSON.stringify({
|
|
322
|
+
...bootstrapRequest,
|
|
323
|
+
proof: await identityAuth.signSessionProof({
|
|
324
|
+
purpose: "deviceBootstrap",
|
|
325
|
+
origin: new URL(args.trellisUrl).origin,
|
|
326
|
+
unsignedRequest: bootstrapRequest,
|
|
327
|
+
}),
|
|
328
|
+
}),
|
|
329
|
+
});
|
|
330
|
+
if (!bootstrapResponse.ok) {
|
|
331
|
+
throw await decodeTrellisHttpError(bootstrapResponse);
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
state: "ready",
|
|
335
|
+
sessionIdentity,
|
|
336
|
+
bundle: await bootstrapResponse.json(),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (state === "rejected") {
|
|
340
|
+
throw new Error("device activation rejected");
|
|
341
|
+
}
|
|
342
|
+
const activation = Reflect.get(body, "activation");
|
|
343
|
+
const currentReviewId = activation?.reviewId;
|
|
344
|
+
const serverNow = Reflect.get(body, "serverNow");
|
|
345
|
+
const expiresAt = activation?.expiresAt;
|
|
346
|
+
if (typeof currentReviewId !== "string" ||
|
|
347
|
+
typeof serverNow !== "number" ||
|
|
348
|
+
typeof expiresAt !== "number") {
|
|
349
|
+
throw new Error("Invalid pending device activation response");
|
|
350
|
+
}
|
|
351
|
+
if (reviewId !== undefined && currentReviewId !== reviewId) {
|
|
352
|
+
throw new Error("device activation review expired");
|
|
353
|
+
}
|
|
354
|
+
if (reviewId === undefined) {
|
|
355
|
+
reviewId = currentReviewId;
|
|
356
|
+
reviewDeadline = performance.now() + Math.max(0, expiresAt - serverNow);
|
|
357
|
+
}
|
|
358
|
+
const retryAfterMs = typeof activation?.retryAfterMs === "number"
|
|
359
|
+
? activation.retryAfterMs
|
|
360
|
+
: pollIntervalMs;
|
|
361
|
+
await sleep(Math.min(Math.max(pollIntervalMs, retryAfterMs), Math.max(0, (reviewDeadline ?? performance.now()) - performance.now())), args.signal);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
export function createDeviceActivationClient(client) {
|
|
365
|
+
return {
|
|
366
|
+
resolveDeviceUserAuthorities(input) {
|
|
367
|
+
return client.authDeviceUserAuthoritiesResolve(input).start().orThrow();
|
|
368
|
+
},
|
|
369
|
+
listDeviceActivations(input) {
|
|
370
|
+
return client.authDeviceUserAuthoritiesList(input).orThrow();
|
|
371
|
+
},
|
|
372
|
+
revokeDeviceActivation(input) {
|
|
373
|
+
return client.authDeviceUserAuthoritiesRevoke(input).orThrow();
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A decoded Trellis HTTP error envelope. */
|
|
2
|
+
export declare const INVALID_HTTP_ERROR_ENVELOPE = "invalid_http_error_envelope";
|
|
3
|
+
/** A decoded Trellis HTTP error envelope. */
|
|
4
|
+
export declare class TrellisHttpError extends Error {
|
|
5
|
+
/** HTTP status returned by Trellis. */
|
|
6
|
+
readonly status: number;
|
|
7
|
+
/** Exact machine-readable Trellis error code. */
|
|
8
|
+
readonly code: string;
|
|
9
|
+
constructor(status: number, code: string);
|
|
10
|
+
}
|
|
11
|
+
/** Decode one authoritative Trellis HTTP error envelope. */
|
|
12
|
+
export declare function decodeTrellisHttpError(response: Response): Promise<TrellisHttpError>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** A decoded Trellis HTTP error envelope. */
|
|
2
|
+
export const INVALID_HTTP_ERROR_ENVELOPE = "invalid_http_error_envelope";
|
|
3
|
+
/** A decoded Trellis HTTP error envelope. */
|
|
4
|
+
export class TrellisHttpError extends Error {
|
|
5
|
+
/** HTTP status returned by Trellis. */
|
|
6
|
+
status;
|
|
7
|
+
/** Exact machine-readable Trellis error code. */
|
|
8
|
+
code;
|
|
9
|
+
constructor(status, code) {
|
|
10
|
+
super(`Trellis HTTP ${status}: ${code}`);
|
|
11
|
+
this.name = "TrellisHttpError";
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Decode one authoritative Trellis HTTP error envelope. */
|
|
17
|
+
export async function decodeTrellisHttpError(response) {
|
|
18
|
+
let code = INVALID_HTTP_ERROR_ENVELOPE;
|
|
19
|
+
try {
|
|
20
|
+
const body = await response.json();
|
|
21
|
+
if (typeof body === "object" && body !== null && "error" in body &&
|
|
22
|
+
typeof body.error === "object" && body.error !== null &&
|
|
23
|
+
"code" in body.error && typeof body.error.code === "string" &&
|
|
24
|
+
body.error.code.length > 0) {
|
|
25
|
+
code = body.error.code;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// The local code keeps malformed responses distinct from server error codes.
|
|
30
|
+
}
|
|
31
|
+
return new TrellisHttpError(response.status, code);
|
|
32
|
+
}
|
package/auth/keys.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function pkcs8FromEd25519Seed(seed32: Uint8Array): Uint8Array;
|
|
2
|
+
export declare function publicKeyBase64urlFromSeed(seed32: Uint8Array): string;
|
|
3
|
+
export declare function importEd25519PrivateKeyFromSeedBase64url(seedBase64url: string): Promise<CryptoKey>;
|
|
4
|
+
export declare function publicKeyBase64urlFromPrivateKey(privateKey: CryptoKey): Promise<string>;
|
|
5
|
+
export declare function importEd25519PublicKeyFromBase64url(publicKeyBase64url: string): Promise<CryptoKey>;
|
package/auth/keys.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import nacl from "tweetnacl";
|
|
2
|
+
import { base64urlDecode, base64urlEncode, toArrayBuffer } from "./utils.js";
|
|
3
|
+
const ED25519_PKCS8_PREFIX = Uint8Array.from([
|
|
4
|
+
0x30,
|
|
5
|
+
0x2e,
|
|
6
|
+
0x02,
|
|
7
|
+
0x01,
|
|
8
|
+
0x00,
|
|
9
|
+
0x30,
|
|
10
|
+
0x05,
|
|
11
|
+
0x06,
|
|
12
|
+
0x03,
|
|
13
|
+
0x2b,
|
|
14
|
+
0x65,
|
|
15
|
+
0x70,
|
|
16
|
+
0x04,
|
|
17
|
+
0x22,
|
|
18
|
+
0x04,
|
|
19
|
+
0x20,
|
|
20
|
+
]);
|
|
21
|
+
export function pkcs8FromEd25519Seed(seed32) {
|
|
22
|
+
if (seed32.length !== 32) {
|
|
23
|
+
throw new Error(`Invalid Ed25519 seed length: ${seed32.length} (expected 32)`);
|
|
24
|
+
}
|
|
25
|
+
const pkcs8 = new Uint8Array(ED25519_PKCS8_PREFIX.length + seed32.length);
|
|
26
|
+
pkcs8.set(ED25519_PKCS8_PREFIX, 0);
|
|
27
|
+
pkcs8.set(seed32, ED25519_PKCS8_PREFIX.length);
|
|
28
|
+
return pkcs8;
|
|
29
|
+
}
|
|
30
|
+
function validateEd25519Seed(seed32) {
|
|
31
|
+
if (seed32.length !== 32) {
|
|
32
|
+
throw new Error(`Invalid Ed25519 seed length: ${seed32.length} (expected 32)`);
|
|
33
|
+
}
|
|
34
|
+
return seed32;
|
|
35
|
+
}
|
|
36
|
+
function ed25519KeyPairFromSeed(seed32) {
|
|
37
|
+
return nacl.sign.keyPair.fromSeed(validateEd25519Seed(seed32));
|
|
38
|
+
}
|
|
39
|
+
export function publicKeyBase64urlFromSeed(seed32) {
|
|
40
|
+
return base64urlEncode(ed25519KeyPairFromSeed(seed32).publicKey);
|
|
41
|
+
}
|
|
42
|
+
export async function importEd25519PrivateKeyFromSeedBase64url(seedBase64url) {
|
|
43
|
+
const seed = base64urlDecode(seedBase64url);
|
|
44
|
+
if (seed.length !== 32) {
|
|
45
|
+
throw new Error(`Invalid Ed25519 seed length: ${seed.length} (expected 32)`);
|
|
46
|
+
}
|
|
47
|
+
const pkcs8 = pkcs8FromEd25519Seed(seed);
|
|
48
|
+
return await crypto.subtle.importKey("pkcs8", toArrayBuffer(pkcs8), { name: "Ed25519" }, true, ["sign"]);
|
|
49
|
+
}
|
|
50
|
+
export async function publicKeyBase64urlFromPrivateKey(privateKey) {
|
|
51
|
+
const jwk = await crypto.subtle.exportKey("jwk", privateKey);
|
|
52
|
+
if (typeof jwk.x !== "string" || jwk.x.length === 0) {
|
|
53
|
+
throw new Error("Failed to derive Ed25519 public key (missing JWK.x)");
|
|
54
|
+
}
|
|
55
|
+
return jwk.x;
|
|
56
|
+
}
|
|
57
|
+
export async function importEd25519PublicKeyFromBase64url(publicKeyBase64url) {
|
|
58
|
+
const raw = base64urlDecode(publicKeyBase64url);
|
|
59
|
+
if (raw.length !== 32) {
|
|
60
|
+
throw new Error(`Invalid Ed25519 public key length: ${raw.length} (expected 32)`);
|
|
61
|
+
}
|
|
62
|
+
return await crypto.subtle.importKey("raw", toArrayBuffer(raw), { name: "Ed25519" }, true, ["verify"]);
|
|
63
|
+
}
|
package/auth/mod.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol-owned Trellis session keys and proof helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
export { type AuthorizationContextBundle, AuthorizationContextBundleSchema, AuthorizationContextCache, AuthorizationContextRefreshError, AuthorizationProviderCache, type AuthorizationProviderEvent, type AuthorizationProviderRequest, type AuthorizationRoutingMaterial, type AuthorizationRuntimeBinding, refreshAuthorizationContext, refreshAuthorizationContextWithMetadata, startAuthorizationContextRefresh, type VerifiedAuthorizationContext, } from "./authorization_context.ts";
|
|
7
|
+
export { type BrowserAuthRecoveryClassification, type BrowserAuthRecoveryKind, classifyBrowserAuthError, isRecoverableBrowserAuthError, } from "./browser_recovery.ts";
|
|
8
|
+
export { buildDeviceActivationPayload, deriveDeviceConfirmationCode, deriveDeviceIdentity, waitForDeviceActivation, } from "./device_activation.ts";
|
|
9
|
+
export { fetchPortalFlowState, portalFlowIdFromUrl, portalProviderLoginUrl, portalRedirectLocation, submitPortalApproval, } from "./browser/portal.ts";
|
|
10
|
+
export type { DeviceUserAuthoritiesResolveOutput, DeviceUserAuthoritiesResolveProgress, } from "../internal_sdk/generated/apis/auth/mod.js";
|
|
11
|
+
export { buildEventProofInput, buildProofInput, createEventProof, createProof, type EventProofParams, type ProofParams, verifyEventProof, verifyProof, } from "./proof.ts";
|
|
12
|
+
export { parseSessionProof, SESSION_PROOF_FORMAT_V1, type SessionProof, type SessionProofInput, type SessionProofPolicy, type SessionProofPurpose, sessionProofRequestDigest, sessionProofSigningDigest, signSessionProof, verifySessionProof, } from "./session_proof.ts";
|
|
13
|
+
export { createAuth, type NatsConnectOptions, type TrellisAuth, } from "./session_auth.ts";
|
|
14
|
+
export { correctedIatSeconds, estimateMidpointClockOffsetMs } from "./time.ts";
|
|
15
|
+
export { trellisIdFromOriginId } from "./trellis_id.ts";
|
|
16
|
+
export { base64urlDecode, base64urlEncode, canonicalizeJsonValue, sha256, toArrayBuffer, utf8, } from "./utils.ts";
|