@astrale-os/cli 1.0.0-beta.32 → 1.0.0-beta.43
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/CHANGELOG.md +928 -0
- package/README.md +53 -43
- package/THIRD-PARTY-NOTICES.md +37 -1
- package/dist/astrale.js +349631 -83669
- package/dist/public/connect-core.js +16 -47
- package/dist/public/keys/index.js +1 -1
- package/dist/types/admin/contract.d.ts +11 -0
- package/dist/types/admin/graph/index.d.ts +1 -0
- package/dist/types/admin/graph/nodes.d.ts +12 -0
- package/dist/types/admin/instance/client.d.ts +2 -0
- package/dist/types/connection/lifetime.d.ts +1 -1
- package/dist/types/errors.d.ts +3 -0
- package/dist/types/lib/proc.d.ts +1 -0
- package/dist/types/lib/update.d.ts +15 -7
- package/package.json +8 -6
- package/studio/client/dist/assets/index-5C1uT6ML.css +2 -0
- package/studio/client/dist/assets/index-C4kNlvwF.js +80 -0
- package/studio/client/dist/assets/schema-studio-BR9d0faB.js +8 -0
- package/studio/client/dist/index.html +13 -2
- package/studio/package.json +2 -1
- package/studio/server/agent/ask.test.ts +7 -1
- package/studio/server/agent/bridge/grant.ts +15 -3
- package/studio/server/agent/prompts/system.test.ts +5 -0
- package/studio/server/agent/prompts/system.ts +19 -4
- package/studio/server/agent/routes.test.ts +39 -1
- package/studio/server/agent/routes.ts +3 -0
- package/studio/server/agent/run/coordinator.test.ts +7 -1
- package/studio/server/agent/run/preparation.ts +1 -0
- package/studio/server/agent/run/transcript.test.ts +47 -2
- package/studio/server/agent/run/transcript.ts +32 -3
- package/studio/server/api/documents.test.ts +31 -0
- package/studio/server/api/documents.ts +35 -1
- package/studio/server/api-agent-loadout.test.ts +7 -1
- package/studio/server/api.test.ts +7 -1
- package/studio/server/cache.ts +4 -4
- package/studio/server/detect.ts +9 -12
- package/studio/server/domain.test.ts +83 -3
- package/studio/server/domain.ts +205 -27
- package/studio/server/handoff/service.ts +1 -1
- package/studio/server/index.ts +5 -8
- package/studio/server/introspect/extractor.ts +111 -17
- package/studio/server/introspect/overlay-tsmorph.test.ts +9 -9
- package/studio/server/introspect/overlay.ts +1 -1
- package/studio/server/introspect/runtime.ts +9 -1
- package/studio/server/introspect/source-overlay/spans.ts +2 -5
- package/studio/server/lifecycle.ts +1 -1
- package/studio/server/state/baseline.test.ts +14 -0
- package/studio/server/state/baseline.ts +16 -2
- package/studio/server/watch.test.ts +2 -1
- package/studio/server/watch.ts +5 -2
- package/studio/server/workspace/create.ts +9 -8
- package/studio/server/workspace-state.ts +4 -6
- package/studio/server/workspace-watch.ts +33 -22
- package/studio/shared/contracts/agent.ts +3 -0
- package/viewer/dist/main.js +12 -12
- package/src/__tests__/fixtures/publication.ts +0 -20
- package/src/admin/.spec/api.d.ts +0 -3
- package/src/admin/.spec/architecture.md +0 -22
- package/src/admin/.spec/icon.svg +0 -3
- package/src/admin/.spec/layout.ts +0 -6
- package/src/admin/__tests__/fixture.ts +0 -25
- package/src/admin/catalog/.spec/api.d.ts +0 -63
- package/src/admin/catalog/.spec/architecture.md +0 -7
- package/src/admin/catalog/.spec/icon.svg +0 -3
- package/src/admin/catalog/.spec/layout.ts +0 -6
- package/src/admin/catalog/__tests__/client.test.ts +0 -245
- package/src/admin/catalog/client.ts +0 -195
- package/src/admin/catalog/index.ts +0 -13
- package/src/admin/catalog/model.ts +0 -41
- package/src/admin/catalog/tsconfig.json +0 -8
- package/src/admin/contract.ts +0 -47
- package/src/admin/graph/.spec/api.d.ts +0 -22
- package/src/admin/graph/.spec/architecture.md +0 -5
- package/src/admin/graph/.spec/icon.svg +0 -3
- package/src/admin/graph/.spec/layout.ts +0 -3
- package/src/admin/graph/index.ts +0 -6
- package/src/admin/graph/nodes.ts +0 -56
- package/src/admin/index.ts +0 -2
- package/src/admin/instance/.spec/api.d.ts +0 -62
- package/src/admin/instance/.spec/architecture.md +0 -10
- package/src/admin/instance/.spec/icon.svg +0 -3
- package/src/admin/instance/.spec/layout.ts +0 -6
- package/src/admin/instance/__tests__/client.test.ts +0 -273
- package/src/admin/instance/client.ts +0 -191
- package/src/admin/instance/index.ts +0 -15
- package/src/admin/instance/model.ts +0 -59
- package/src/admin/instance/tsconfig.json +0 -8
- package/src/commands/__tests__/admin-instance.test.ts +0 -91
- package/src/commands/__tests__/auth-login.test.ts +0 -231
- package/src/commands/__tests__/auth-token.test.ts +0 -223
- package/src/commands/__tests__/call-describe.test.ts +0 -80
- package/src/commands/__tests__/call.test.ts +0 -63
- package/src/commands/__tests__/domain-install-operation.test.ts +0 -145
- package/src/commands/__tests__/domain-install-owned.test.ts +0 -191
- package/src/commands/__tests__/domain-list.test.ts +0 -133
- package/src/commands/__tests__/domain-uninstall.test.ts +0 -53
- package/src/commands/__tests__/install-direct.test.ts +0 -128
- package/src/commands/__tests__/install-identity-override.test.ts +0 -76
- package/src/commands/__tests__/instance-bookmark.test.ts +0 -166
- package/src/commands/__tests__/instance-list-admin.test.ts +0 -26
- package/src/commands/__tests__/instance-list-rows.test.ts +0 -64
- package/src/commands/__tests__/instance-status.test.ts +0 -139
- package/src/commands/__tests__/instance-use.test.ts +0 -67
- package/src/commands/__tests__/introspect-parse.test.ts +0 -100
- package/src/commands/__tests__/logs.test.ts +0 -345
- package/src/commands/__tests__/read-commands.test.ts +0 -377
- package/src/commands/__tests__/setup-plan.test.ts +0 -61
- package/src/commands/__tests__/studio-descriptor.test.ts +0 -22
- package/src/commands/__tests__/token-ttl.test.ts +0 -66
- package/src/commands/__tests__/update.test.ts +0 -95
- package/src/commands/__tests__/view-build.test.ts +0 -58
- package/src/commands/__tests__/view.test.ts +0 -197
- package/src/commands/admin/status.ts +0 -61
- package/src/commands/admin/use.ts +0 -100
- package/src/commands/auth/login.ts +0 -82
- package/src/commands/auth/logout.ts +0 -53
- package/src/commands/auth/status.ts +0 -86
- package/src/commands/auth/token.ts +0 -174
- package/src/commands/browser.ts +0 -236
- package/src/commands/call-describe.ts +0 -123
- package/src/commands/call.ts +0 -230
- package/src/commands/domain/install.ts +0 -575
- package/src/commands/domain/list.ts +0 -155
- package/src/commands/domain/publish.ts +0 -136
- package/src/commands/domain/uninstall.ts +0 -128
- package/src/commands/get.ts +0 -69
- package/src/commands/identity/__tests__/register.test.ts +0 -93
- package/src/commands/identity/create.ts +0 -35
- package/src/commands/identity/delete.ts +0 -24
- package/src/commands/identity/export.ts +0 -46
- package/src/commands/identity/import.ts +0 -61
- package/src/commands/identity/list.ts +0 -56
- package/src/commands/identity/register.ts +0 -195
- package/src/commands/identity/sync.ts +0 -38
- package/src/commands/identity/unsync.ts +0 -34
- package/src/commands/identity/use.ts +0 -24
- package/src/commands/identity/whoami.ts +0 -34
- package/src/commands/idp/add.ts +0 -150
- package/src/commands/idp/list.ts +0 -57
- package/src/commands/idp/refresh.ts +0 -38
- package/src/commands/idp/remove.ts +0 -36
- package/src/commands/idp/show.ts +0 -29
- package/src/commands/instance/active.ts +0 -73
- package/src/commands/instance/bookmark.ts +0 -101
- package/src/commands/instance/create.ts +0 -61
- package/src/commands/instance/delete.ts +0 -69
- package/src/commands/instance/forget.ts +0 -26
- package/src/commands/instance/list.ts +0 -197
- package/src/commands/instance/status.ts +0 -92
- package/src/commands/instance/use.ts +0 -263
- package/src/commands/introspect.ts +0 -129
- package/src/commands/logs.ts +0 -371
- package/src/commands/mutate.ts +0 -122
- package/src/commands/query.ts +0 -142
- package/src/commands/session/analyze.ts +0 -72
- package/src/commands/session/list.ts +0 -36
- package/src/commands/setup.ts +0 -54
- package/src/commands/status.ts +0 -60
- package/src/commands/studio.ts +0 -434
- package/src/commands/token.ts +0 -121
- package/src/commands/ui/__tests__/commands.test.ts +0 -152
- package/src/commands/ui/add.ts +0 -51
- package/src/commands/ui/doctor.ts +0 -21
- package/src/commands/ui/init.ts +0 -38
- package/src/commands/ui/list.ts +0 -26
- package/src/commands/ui/preset-apply.ts +0 -19
- package/src/commands/ui/preset-list.ts +0 -12
- package/src/commands/ui/shared.ts +0 -25
- package/src/commands/update.ts +0 -356
- package/src/commands/use.ts +0 -86
- package/src/commands/view-serve.ts +0 -26
- package/src/commands/view.ts +0 -658
- package/src/connect-core.test.ts +0 -38
- package/src/connect-core.ts +0 -42
- package/src/connection/.spec/api.d.ts +0 -110
- package/src/connection/.spec/architecture.md +0 -58
- package/src/connection/.spec/capabilities/connection.ts +0 -13
- package/src/connection/.spec/examples/call.ts +0 -7
- package/src/connection/.spec/flows/command.ts +0 -20
- package/src/connection/.spec/flows/session.ts +0 -62
- package/src/connection/.spec/laws/connection.ts +0 -213
- package/src/connection/.spec/layout.ts +0 -22
- package/src/connection/__tests__/auth.test.ts +0 -129
- package/src/connection/__tests__/ca-fetch.test.ts +0 -60
- package/src/connection/__tests__/call.test.ts +0 -17
- package/src/connection/__tests__/credential.test.ts +0 -254
- package/src/connection/__tests__/errors.test.ts +0 -577
- package/src/connection/__tests__/exchange.test.ts +0 -404
- package/src/connection/__tests__/failure-fixtures.ts +0 -39
- package/src/connection/__tests__/failure-safety.test.ts +0 -75
- package/src/connection/__tests__/fixtures/localhost-cert.pem +0 -19
- package/src/connection/__tests__/fixtures/localhost-key.base64 +0 -1
- package/src/connection/__tests__/managed.test.ts +0 -39
- package/src/connection/__tests__/reasons.test.ts +0 -78
- package/src/connection/__tests__/self.test.ts +0 -71
- package/src/connection/__tests__/session.test.ts +0 -144
- package/src/connection/__tests__/target.test.ts +0 -95
- package/src/connection/auth.ts +0 -265
- package/src/connection/call.ts +0 -7
- package/src/connection/command.ts +0 -67
- package/src/connection/credential.ts +0 -138
- package/src/connection/errors.ts +0 -1
- package/src/connection/exchange.ts +0 -394
- package/src/connection/failure/classify.ts +0 -110
- package/src/connection/failure/index.ts +0 -18
- package/src/connection/failure/model.ts +0 -28
- package/src/connection/failure/render.ts +0 -116
- package/src/connection/index.ts +0 -8
- package/src/connection/lifetime.ts +0 -25
- package/src/connection/reasons.ts +0 -209
- package/src/connection/self.ts +0 -138
- package/src/connection/session.ts +0 -203
- package/src/connection/target.ts +0 -112
- package/src/connection/tsconfig.json +0 -6
- package/src/errors.ts +0 -66
- package/src/graph/.spec/api.d.ts +0 -41
- package/src/graph/.spec/laws/documents.ts +0 -24
- package/src/graph/.spec/laws/projection.ts +0 -13
- package/src/graph/.spec/layout.ts +0 -14
- package/src/graph/__tests__/mutation.test.ts +0 -51
- package/src/graph/__tests__/projection.test.ts +0 -22
- package/src/graph/__tests__/query.test.ts +0 -180
- package/src/graph/class.ts +0 -30
- package/src/graph/index.ts +0 -4
- package/src/graph/mutation.ts +0 -46
- package/src/graph/projection.ts +0 -21
- package/src/graph/query.ts +0 -95
- package/src/graph/tsconfig.json +0 -6
- package/src/identity/.history/ard/bootstrap-root-transfer.md +0 -57
- package/src/identity/.history/ard/domain-mediated-registration.md +0 -36
- package/src/identity/.spec/api.d.ts +0 -124
- package/src/identity/.spec/architecture.md +0 -33
- package/src/identity/.spec/capabilities/identity.ts +0 -7
- package/src/identity/.spec/flows/bootstrap-root.ts +0 -79
- package/src/identity/.spec/flows/domain-mediated-registration.ts +0 -51
- package/src/identity/.spec/flows/import.ts +0 -29
- package/src/identity/.spec/laws/identity.ts +0 -56
- package/src/identity/.spec/layout.ts +0 -13
- package/src/identity/__tests__/fixtures/registry-journey.ts +0 -65
- package/src/identity/__tests__/registration.test.ts +0 -64
- package/src/identity/__tests__/registry.test.ts +0 -74
- package/src/identity/__tests__/transfer.test.ts +0 -198
- package/src/identity/index.ts +0 -23
- package/src/identity/registration.ts +0 -72
- package/src/identity/registry.ts +0 -181
- package/src/identity/transfer.ts +0 -187
- package/src/identity/tsconfig.json +0 -6
- package/src/index.ts +0 -1
- package/src/keys/.spec/api.d.ts +0 -74
- package/src/keys/.spec/architecture.md +0 -13
- package/src/keys/.spec/laws/keys.ts +0 -43
- package/src/keys/.spec/layout.ts +0 -6
- package/src/keys/__tests__/algorithm.test.ts +0 -39
- package/src/keys/__tests__/keys.test.ts +0 -172
- package/src/keys/algorithm.ts +0 -33
- package/src/keys/credential.ts +0 -113
- package/src/keys/index.ts +0 -14
- package/src/keys/pair.ts +0 -219
- package/src/keys/tsconfig.json +0 -6
- package/src/lib/__tests__/admin-target.test.ts +0 -184
- package/src/lib/__tests__/binary.test.ts +0 -70
- package/src/lib/__tests__/browser-retention.test.ts +0 -212
- package/src/lib/__tests__/command-dx.test.ts +0 -102
- package/src/lib/__tests__/concurrency.test.ts +0 -62
- package/src/lib/__tests__/config.test.ts +0 -81
- package/src/lib/__tests__/design.test.ts +0 -99
- package/src/lib/__tests__/domain-publication.test.ts +0 -138
- package/src/lib/__tests__/format.test.ts +0 -22
- package/src/lib/__tests__/idp-session.driver.ts +0 -60
- package/src/lib/__tests__/idp-session.test.ts +0 -404
- package/src/lib/__tests__/idp.test.ts +0 -415
- package/src/lib/__tests__/instance-candidates.test.ts +0 -146
- package/src/lib/__tests__/instance-target.test.ts +0 -213
- package/src/lib/__tests__/instance.test.ts +0 -192
- package/src/lib/__tests__/local-status.test.ts +0 -202
- package/src/lib/__tests__/log-errors.test.ts +0 -64
- package/src/lib/__tests__/log.test.ts +0 -31
- package/src/lib/__tests__/meta.test.ts +0 -55
- package/src/lib/__tests__/output.test.ts +0 -162
- package/src/lib/__tests__/panel.test.ts +0 -40
- package/src/lib/__tests__/port.test.ts +0 -44
- package/src/lib/__tests__/prompt.test.ts +0 -25
- package/src/lib/__tests__/sdk-deps.test.ts +0 -68
- package/src/lib/__tests__/self.test.ts +0 -50
- package/src/lib/__tests__/skills.test.ts +0 -834
- package/src/lib/__tests__/studio-server-deps.test.ts +0 -111
- package/src/lib/__tests__/table.test.ts +0 -53
- package/src/lib/__tests__/update.test.ts +0 -511
- package/src/lib/__tests__/use-target.test.ts +0 -56
- package/src/lib/__tests__/validation.test.ts +0 -34
- package/src/lib/__tests__/view-assets.test.ts +0 -171
- package/src/lib/__tests__/view-external-open-intent.test.ts +0 -150
- package/src/lib/__tests__/view-external-open-origins.test.ts +0 -27
- package/src/lib/__tests__/view-open-intent.test.ts +0 -280
- package/src/lib/__tests__/view-port-allocation.test.ts +0 -82
- package/src/lib/__tests__/view-server.test.ts +0 -91
- package/src/lib/__tests__/view-session.test.ts +0 -100
- package/src/lib/__tests__/view-snapshot.test.ts +0 -77
- package/src/lib/admin-domain.ts +0 -43
- package/src/lib/admin-instance.ts +0 -56
- package/src/lib/admin-target.ts +0 -290
- package/src/lib/binary.ts +0 -127
- package/src/lib/browser-retention.ts +0 -210
- package/src/lib/browser.ts +0 -150
- package/src/lib/ca-fetch.ts +0 -133
- package/src/lib/command-dx.ts +0 -191
- package/src/lib/concurrency.ts +0 -31
- package/src/lib/config.ts +0 -57
- package/src/lib/credential-lifetime.ts +0 -24
- package/src/lib/domain-publication.ts +0 -104
- package/src/lib/failure-debug.ts +0 -26
- package/src/lib/format.ts +0 -4
- package/src/lib/idp-session.ts +0 -156
- package/src/lib/idp.ts +0 -926
- package/src/lib/instance-candidates.ts +0 -49
- package/src/lib/instance-target.ts +0 -197
- package/src/lib/instance.ts +0 -468
- package/src/lib/invocation.ts +0 -11
- package/src/lib/local-status.ts +0 -152
- package/src/lib/log.ts +0 -143
- package/src/lib/login-flow.ts +0 -201
- package/src/lib/meta.ts +0 -90
- package/src/lib/output.ts +0 -238
- package/src/lib/panel.ts +0 -61
- package/src/lib/port.ts +0 -41
- package/src/lib/proc.ts +0 -87
- package/src/lib/prompt.ts +0 -136
- package/src/lib/provision-instance.ts +0 -139
- package/src/lib/sdk-deps.ts +0 -104
- package/src/lib/self.ts +0 -22
- package/src/lib/skills/lock.ts +0 -117
- package/src/lib/skills/sync.ts +0 -878
- package/src/lib/skills.ts +0 -120
- package/src/lib/table.ts +0 -62
- package/src/lib/update.ts +0 -568
- package/src/lib/use-target.ts +0 -24
- package/src/lib/validation.ts +0 -59
- package/src/lib/view/assets.ts +0 -87
- package/src/lib/view/external-open-intent.ts +0 -41
- package/src/lib/view/external-open-origins.ts +0 -37
- package/src/lib/view/host-capabilities.ts +0 -17
- package/src/lib/view/open-intent.ts +0 -67
- package/src/lib/view/port-allocation.ts +0 -19
- package/src/lib/view/resolve.ts +0 -111
- package/src/lib/view/server.ts +0 -259
- package/src/lib/view/session.ts +0 -154
- package/src/lib/view/snapshot.ts +0 -101
- package/src/paths/.spec/api.d.ts +0 -13
- package/src/paths/.spec/architecture.md +0 -5
- package/src/paths/.spec/layout.ts +0 -3
- package/src/paths/index.ts +0 -13
- package/src/program/.spec/api.d.ts +0 -43
- package/src/program/.spec/architecture.md +0 -18
- package/src/program/.spec/laws/program.ts +0 -29
- package/src/program/.spec/layout.ts +0 -14
- package/src/program/__tests__/program.test.ts +0 -556
- package/src/program/argv.ts +0 -14
- package/src/program/build.ts +0 -205
- package/src/program/command.ts +0 -43
- package/src/program/index.ts +0 -3
- package/src/program/options.ts +0 -41
- package/src/program/registry.ts +0 -64
- package/src/program/tsconfig.json +0 -6
- package/src/setup/__tests__/instance-step.test.ts +0 -256
- package/src/setup/__tests__/util.test.ts +0 -29
- package/src/setup/engine.ts +0 -83
- package/src/setup/render.ts +0 -107
- package/src/setup/steps/admin.ts +0 -93
- package/src/setup/steps/agent-browser.ts +0 -81
- package/src/setup/steps/auth.ts +0 -54
- package/src/setup/steps/domain.ts +0 -68
- package/src/setup/steps/index.ts +0 -18
- package/src/setup/steps/instance.ts +0 -206
- package/src/setup/steps/skills-bridge.ts +0 -70
- package/src/setup/steps/skills.ts +0 -61
- package/src/setup/types.ts +0 -61
- package/src/setup/util.ts +0 -34
- package/src/state/.spec/api.d.ts +0 -152
- package/src/state/.spec/architecture.md +0 -45
- package/src/state/.spec/capabilities/state.ts +0 -7
- package/src/state/.spec/flows/identity-update.ts +0 -22
- package/src/state/.spec/laws/state.ts +0 -73
- package/src/state/.spec/layout.ts +0 -15
- package/src/state/__tests__/exchange-credentials.test.ts +0 -341
- package/src/state/__tests__/files.test.ts +0 -136
- package/src/state/__tests__/fixtures/identity-transition.ts +0 -37
- package/src/state/__tests__/fixtures/session-route-process.ts +0 -93
- package/src/state/__tests__/identities.test.ts +0 -142
- package/src/state/__tests__/paths.test.ts +0 -28
- package/src/state/__tests__/session-routes.test.ts +0 -138
- package/src/state/exchange-credentials.ts +0 -250
- package/src/state/files.ts +0 -170
- package/src/state/identities.ts +0 -229
- package/src/state/index.ts +0 -31
- package/src/state/paths.ts +0 -64
- package/src/state/session-routes.ts +0 -34
- package/src/state/tsconfig.json +0 -6
- package/src/telemetry/__tests__/analyze-log.test.ts +0 -38
- package/src/telemetry/__tests__/gate.test.ts +0 -63
- package/src/telemetry/__tests__/recorder.test.ts +0 -111
- package/src/telemetry/__tests__/redact.test.ts +0 -79
- package/src/telemetry/__tests__/retention.test.ts +0 -267
- package/src/telemetry/__tests__/session.test.ts +0 -166
- package/src/telemetry/__tests__/settings.test.ts +0 -102
- package/src/telemetry/__tests__/store-scan.test.ts +0 -128
- package/src/telemetry/__tests__/trigger.test.ts +0 -78
- package/src/telemetry/adapters/__tests__/claude-code.test.ts +0 -89
- package/src/telemetry/adapters/__tests__/codex.test.ts +0 -138
- package/src/telemetry/adapters/__tests__/index.test.ts +0 -88
- package/src/telemetry/adapters/claude-code.ts +0 -90
- package/src/telemetry/adapters/codex.ts +0 -160
- package/src/telemetry/adapters/index.ts +0 -45
- package/src/telemetry/adapters/types.ts +0 -21
- package/src/telemetry/analyze.ts +0 -249
- package/src/telemetry/gate.ts +0 -79
- package/src/telemetry/recorder.ts +0 -68
- package/src/telemetry/redact.ts +0 -53
- package/src/telemetry/retention.ts +0 -176
- package/src/telemetry/session.ts +0 -94
- package/src/telemetry/settings.ts +0 -79
- package/src/telemetry/store.ts +0 -174
- package/src/telemetry/trigger.ts +0 -107
- package/src/telemetry/types.ts +0 -64
- package/src/test-utils.ts +0 -18
- package/src/ui/.spec/api.d.ts +0 -14
- package/src/ui/.spec/architecture.md +0 -14
- package/src/ui/.spec/laws.ts +0 -50
- package/src/ui/.spec/layout.ts +0 -16
- package/src/ui/__tests__/ui.test.ts +0 -1742
- package/src/ui/index.ts +0 -13
- package/src/ui/lock.ts +0 -89
- package/src/ui/model.ts +0 -83
- package/src/ui/operations.ts +0 -1044
- package/src/ui/project.ts +0 -333
- package/src/ui/release.ts +0 -398
- package/src/ui/runner.ts +0 -18
- package/studio/client/dist/assets/index-BFVFs_8x.js +0 -81
- package/studio/client/dist/assets/index-DuB9iUdu.css +0 -2
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +0 -8
- package/studio/tsconfig.json +0 -24
- package/viewer/tsconfig.json +0 -13
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import { decodeJwt, decodeProtectedHeader } from 'jose'
|
|
3
|
-
import { access, mkdtemp, rm, stat, writeFile } from 'node:fs/promises'
|
|
4
|
-
import { tmpdir } from 'node:os'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
|
|
7
|
-
import { IdentityKeyMissingError } from '../../errors'
|
|
8
|
-
import {
|
|
9
|
-
generateEd25519Jwk,
|
|
10
|
-
importKeypair,
|
|
11
|
-
keypairPaths,
|
|
12
|
-
listIdentityKeys,
|
|
13
|
-
persistKeypair,
|
|
14
|
-
readKeypair,
|
|
15
|
-
removeKeypair,
|
|
16
|
-
signAs,
|
|
17
|
-
} from '../index'
|
|
18
|
-
|
|
19
|
-
describe('DESIGN — per-identity keys', () => {
|
|
20
|
-
let tmp = ''
|
|
21
|
-
|
|
22
|
-
beforeEach(async () => {
|
|
23
|
-
tmp = await mkdtemp(join(tmpdir(), 'astrale-keys-test-'))
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
afterEach(async () => {
|
|
27
|
-
await rm(tmp, { recursive: true, force: true })
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
/** @evidence TEST-CLI-KEYS-LEGACY-FILENAMES */
|
|
31
|
-
test('keypairPaths routes manager to legacy filenames', () => {
|
|
32
|
-
const p = keypairPaths('manager', tmp)
|
|
33
|
-
expect(p.privatePath.endsWith('manager.private.jwk')).toBe(true)
|
|
34
|
-
expect(p.publicPath.endsWith('manager.public.jwk')).toBe(true)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('keypairPaths routes others to <name>.*.jwk', () => {
|
|
38
|
-
const p = keypairPaths('alice', tmp)
|
|
39
|
-
expect(p.privatePath.endsWith('alice.private.jwk')).toBe(true)
|
|
40
|
-
expect(p.publicPath.endsWith('alice.public.jwk')).toBe(true)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
/** @evidence TEST-CLI-KEYS-PATH-CONFINED */
|
|
44
|
-
test('keypairPaths rejects subjects that escape the selected key directory', () => {
|
|
45
|
-
expect(() => keypairPaths('../alice', tmp)).toThrow(/cannot name a key file/)
|
|
46
|
-
expect(() => keypairPaths('/alice', tmp)).toThrow(/cannot name a key file/)
|
|
47
|
-
expect(() => keypairPaths('', tmp)).toThrow(/cannot name a key file/)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
/** @evidence TEST-CLI-KEYS-PRIVATE-MODE */
|
|
51
|
-
test('persistKeypair writes both files with kid and private mode', async () => {
|
|
52
|
-
const result = await persistKeypair('alice', { keysDir: tmp })
|
|
53
|
-
expect(result.kid).toMatch(/^alice-key-/)
|
|
54
|
-
const { privatePath, publicPath } = keypairPaths('alice', tmp)
|
|
55
|
-
await access(privatePath)
|
|
56
|
-
await access(publicPath)
|
|
57
|
-
expect((await stat(privatePath)).mode & 0o777).toBe(0o600)
|
|
58
|
-
expect((await stat(publicPath)).mode & 0o777).toBe(0o600)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('signAs(alice) works when alice has her own key', async () => {
|
|
62
|
-
await persistKeypair('alice', { keysDir: tmp })
|
|
63
|
-
const jwt = await signAs('alice', tmp)
|
|
64
|
-
expect(jwt.split('.').length).toBe(3)
|
|
65
|
-
const claims = decodeJwt(jwt)
|
|
66
|
-
expect(claims.iat).toBeUndefined()
|
|
67
|
-
expect(claims.exp).toBeNumber()
|
|
68
|
-
expect((claims.exp as number) * 1_000).toBeGreaterThan(Date.now() + 299_000)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
/** @evidence TEST-CLI-KEYS-DISTINGUISHES-KERNEL-ROOT-GRANT */
|
|
72
|
-
test('signAs resolves self-issued Kernel root grants and preserves external self grants', async () => {
|
|
73
|
-
await persistKeypair('manager-principal', { keysDir: tmp })
|
|
74
|
-
await persistKeypair('alice', { keysDir: tmp })
|
|
75
|
-
const kernel = 'https://kernel.example/kernel/host'
|
|
76
|
-
|
|
77
|
-
const root = decodeJwt(
|
|
78
|
-
await signAs('manager-principal', tmp, {
|
|
79
|
-
issuer: kernel,
|
|
80
|
-
audience: kernel,
|
|
81
|
-
}),
|
|
82
|
-
)
|
|
83
|
-
expect(root).toMatchObject({
|
|
84
|
-
iss: kernel,
|
|
85
|
-
sub: 'manager-principal',
|
|
86
|
-
aud: kernel,
|
|
87
|
-
grant: { v: 1, expr: { kind: 'identity', id: 'manager-principal' } },
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const external = decodeJwt(
|
|
91
|
-
await signAs('alice', tmp, {
|
|
92
|
-
issuer: 'https://identity.example',
|
|
93
|
-
audience: kernel,
|
|
94
|
-
}),
|
|
95
|
-
)
|
|
96
|
-
expect(external).toMatchObject({
|
|
97
|
-
iss: 'https://identity.example',
|
|
98
|
-
sub: 'alice',
|
|
99
|
-
aud: kernel,
|
|
100
|
-
grant: { v: 1, expr: { kind: 'identity', self: true } },
|
|
101
|
-
})
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
test('signAs(alice) uses the key file algorithm for EdDSA identities', async () => {
|
|
105
|
-
const { privateJwk, publicJwk } = await generateEd25519Jwk('alice-ed25519')
|
|
106
|
-
const { privatePath, publicPath } = keypairPaths('alice', tmp)
|
|
107
|
-
await writeFile(privatePath, JSON.stringify(privateJwk, null, 2))
|
|
108
|
-
await writeFile(publicPath, JSON.stringify(publicJwk, null, 2))
|
|
109
|
-
|
|
110
|
-
const jwt = await signAs('alice', tmp)
|
|
111
|
-
|
|
112
|
-
expect(decodeProtectedHeader(jwt).alg).toBe('EdDSA')
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
/** @evidence TEST-CLI-KEYS-PAIR-ADMISSION */
|
|
116
|
-
test('import and read prove one matching supported keypair before publication', async () => {
|
|
117
|
-
const alice = await persistKeypair('alice', { keysDir: join(tmp, 'alice-source') })
|
|
118
|
-
const bob = await persistKeypair('bob', { keysDir: join(tmp, 'bob-source') })
|
|
119
|
-
const importedDir = join(tmp, 'imported')
|
|
120
|
-
|
|
121
|
-
await expect(
|
|
122
|
-
importKeypair(
|
|
123
|
-
'alice',
|
|
124
|
-
{ privateJwk: alice.privateJwk, publicJwk: { ...bob.publicJwk, kid: alice.kid } },
|
|
125
|
-
importedDir,
|
|
126
|
-
),
|
|
127
|
-
).rejects.toThrow(/do not form a valid matching pair/)
|
|
128
|
-
await expect(access(keypairPaths('alice', importedDir).privatePath)).rejects.toThrow()
|
|
129
|
-
|
|
130
|
-
const imported = await importKeypair(
|
|
131
|
-
'alice',
|
|
132
|
-
{ privateJwk: alice.privateJwk, publicJwk: alice.publicJwk },
|
|
133
|
-
importedDir,
|
|
134
|
-
)
|
|
135
|
-
expect(imported.kid).toBe(alice.kid)
|
|
136
|
-
expect((await readKeypair('alice', importedDir)).publicJwk).toEqual(alice.publicJwk)
|
|
137
|
-
|
|
138
|
-
await expect(
|
|
139
|
-
importKeypair(
|
|
140
|
-
'unsafe-public',
|
|
141
|
-
{ privateJwk: alice.privateJwk, publicJwk: alice.privateJwk },
|
|
142
|
-
importedDir,
|
|
143
|
-
),
|
|
144
|
-
).rejects.toThrow(/must not contain private key material/)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
/** @evidence TEST-CLI-KEYS-NO-MANAGER-FALLBACK */
|
|
148
|
-
test('signAs(alice) never falls back to the manager key', async () => {
|
|
149
|
-
await persistKeypair('manager', { keysDir: tmp })
|
|
150
|
-
await expect(signAs('alice', tmp)).rejects.toThrow(IdentityKeyMissingError)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
test('signAs errors when no keys at all', async () => {
|
|
154
|
-
await expect(signAs('manager', tmp)).rejects.toThrow(IdentityKeyMissingError)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
test('removeKeypair is idempotent', async () => {
|
|
158
|
-
await persistKeypair('alice', { keysDir: tmp })
|
|
159
|
-
await removeKeypair('alice', tmp)
|
|
160
|
-
await removeKeypair('alice', tmp)
|
|
161
|
-
const { privatePath } = keypairPaths('alice', tmp)
|
|
162
|
-
await expect(access(privatePath)).rejects.toThrow()
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
test('listIdentityKeys returns names with private keys', async () => {
|
|
166
|
-
await persistKeypair('manager', { keysDir: tmp })
|
|
167
|
-
await persistKeypair('alice', { keysDir: tmp })
|
|
168
|
-
await persistKeypair('bob', { keysDir: tmp })
|
|
169
|
-
const names = await listIdentityKeys(tmp)
|
|
170
|
-
expect(names.sort()).toEqual(['alice', 'bob', 'manager'])
|
|
171
|
-
})
|
|
172
|
-
})
|
package/src/keys/algorithm.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { AstraleError } from '../errors'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Resolve the JOSE algorithm to use with this key. Prefers `privateJwk.alg`
|
|
5
|
-
* when present, falls back to inferring from `crv`/`kty` so keys generated
|
|
6
|
-
* by older CLIs that did not stamp `alg`. Throws when neither form resolves.
|
|
7
|
-
*/
|
|
8
|
-
export function inferAlg(privateJwk: Record<string, unknown>, keyPath?: string): string {
|
|
9
|
-
const explicit = privateJwk.alg
|
|
10
|
-
if (typeof explicit === 'string' && explicit.length > 0) {
|
|
11
|
-
if (explicit === 'ES256' || explicit === 'EdDSA') return explicit
|
|
12
|
-
throw unsupportedAlgorithm(explicit, keyPath)
|
|
13
|
-
}
|
|
14
|
-
const crv = privateJwk.crv
|
|
15
|
-
const kty = privateJwk.kty
|
|
16
|
-
if (kty === 'EC' && crv === 'P-256') return 'ES256'
|
|
17
|
-
if (kty === 'OKP' && crv === 'Ed25519') return 'EdDSA'
|
|
18
|
-
const where = keyPath ? ` at ${keyPath}` : ''
|
|
19
|
-
throw new AstraleError(
|
|
20
|
-
'INVALID_KEY_FILE',
|
|
21
|
-
`JWK${where} is missing both \`alg\` and a recognizable \`(kty, crv)\` pair — cannot pick a signing algorithm.`,
|
|
22
|
-
'Re-stamp the file with `"alg": "ES256"` (P-256 EC keys) or `"alg": "EdDSA"` (Ed25519 OKP keys), or regenerate via `astrale domain init` / `astrale identity create`.',
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function unsupportedAlgorithm(algorithm: string, keyPath?: string): AstraleError {
|
|
27
|
-
const where = keyPath ? ` at ${keyPath}` : ''
|
|
28
|
-
return new AstraleError(
|
|
29
|
-
'INVALID_KEY_FILE',
|
|
30
|
-
`JWK${where} selects unsupported signing algorithm ${JSON.stringify(algorithm)}.`,
|
|
31
|
-
'Use an ES256 P-256 key or an EdDSA Ed25519 key.',
|
|
32
|
-
)
|
|
33
|
-
}
|
package/src/keys/credential.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { importJWK, SignJWT, type JWK } from 'jose'
|
|
2
|
-
|
|
3
|
-
import { IdentityKeyMissingError } from '../errors'
|
|
4
|
-
import { KEYS_DIR } from '../state/index'
|
|
5
|
-
import { inferAlg } from './algorithm'
|
|
6
|
-
import { fileExists, keypairPaths, persistKeypair, readKeypair } from './pair'
|
|
7
|
-
|
|
8
|
-
const DEFAULT_MANAGER_ISSUER = 'http://localhost:4400/host'
|
|
9
|
-
|
|
10
|
-
type AuthOptions = {
|
|
11
|
-
readonly issuer?: string
|
|
12
|
-
readonly subject?: string
|
|
13
|
-
readonly kid?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type AuthBinding = {
|
|
17
|
-
readonly credential: string
|
|
18
|
-
readonly publicKey: { readonly jwk: JWK }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function loadSigningMaterial(
|
|
22
|
-
subject: string,
|
|
23
|
-
keysDir: string,
|
|
24
|
-
): Promise<{ privateJwk: JWK; publicJwk: JWK; kid: string }> {
|
|
25
|
-
const { privatePath } = keypairPaths(subject, keysDir)
|
|
26
|
-
if (!(await fileExists(privatePath))) throw new IdentityKeyMissingError(subject)
|
|
27
|
-
|
|
28
|
-
const pair = await readKeypair(subject, keysDir)
|
|
29
|
-
return { ...pair, kid: pair.kid ?? `${subject}-key` }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function signIdentityCredential(options: {
|
|
33
|
-
readonly privateJwk: JWK
|
|
34
|
-
readonly kid: string
|
|
35
|
-
readonly issuer: string
|
|
36
|
-
readonly subject: string
|
|
37
|
-
readonly audience: string
|
|
38
|
-
}): Promise<string> {
|
|
39
|
-
const alg = inferAlg(options.privateJwk as Record<string, unknown>)
|
|
40
|
-
const privateKey = await importJWK(options.privateJwk, alg)
|
|
41
|
-
const expr =
|
|
42
|
-
options.issuer === options.audience
|
|
43
|
-
? { kind: 'identity' as const, id: options.subject }
|
|
44
|
-
: { kind: 'identity' as const, self: true as const }
|
|
45
|
-
return new SignJWT({ grant: { v: 1, expr } })
|
|
46
|
-
.setProtectedHeader({ alg, kid: options.kid })
|
|
47
|
-
.setIssuer(options.issuer)
|
|
48
|
-
.setSubject(options.subject)
|
|
49
|
-
.setAudience(options.audience)
|
|
50
|
-
.setExpirationTime('5m')
|
|
51
|
-
.sign(privateKey)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function persistAuth(
|
|
55
|
-
keysDir: string = KEYS_DIR,
|
|
56
|
-
opts?: AuthOptions,
|
|
57
|
-
): Promise<AuthBinding> {
|
|
58
|
-
const issuer = opts?.issuer ?? DEFAULT_MANAGER_ISSUER
|
|
59
|
-
const subject = opts?.subject ?? 'manager'
|
|
60
|
-
const kid = opts?.kid ?? `${subject}-key`
|
|
61
|
-
const { privateJwk, publicJwk } = await persistKeypair(subject, { keysDir, kid })
|
|
62
|
-
const credential = await signIdentityCredential({
|
|
63
|
-
privateJwk,
|
|
64
|
-
kid,
|
|
65
|
-
issuer,
|
|
66
|
-
subject,
|
|
67
|
-
audience: issuer,
|
|
68
|
-
})
|
|
69
|
-
return { credential, publicKey: { jwk: publicJwk } }
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export async function loadAuth(
|
|
73
|
-
keysDir: string = KEYS_DIR,
|
|
74
|
-
opts?: AuthOptions,
|
|
75
|
-
): Promise<AuthBinding> {
|
|
76
|
-
const issuer = opts?.issuer ?? DEFAULT_MANAGER_ISSUER
|
|
77
|
-
const subject = opts?.subject ?? 'manager'
|
|
78
|
-
const { privateJwk, publicJwk, kid } = await loadSigningMaterial(subject, keysDir)
|
|
79
|
-
const credential = await signIdentityCredential({
|
|
80
|
-
privateJwk,
|
|
81
|
-
kid,
|
|
82
|
-
issuer,
|
|
83
|
-
subject,
|
|
84
|
-
audience: issuer,
|
|
85
|
-
})
|
|
86
|
-
return { credential, publicKey: { jwk: publicJwk } }
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export async function resolveAuth(
|
|
90
|
-
keysDir: string = KEYS_DIR,
|
|
91
|
-
opts?: AuthOptions,
|
|
92
|
-
): Promise<AuthBinding> {
|
|
93
|
-
const subject = opts?.subject ?? 'manager'
|
|
94
|
-
const { privatePath } = keypairPaths(subject, keysDir)
|
|
95
|
-
return (await fileExists(privatePath)) ? loadAuth(keysDir, opts) : persistAuth(keysDir, opts)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export async function signAs(
|
|
99
|
-
subject: string,
|
|
100
|
-
keysDir: string = KEYS_DIR,
|
|
101
|
-
opts?: { readonly issuer?: string; readonly audience?: string; readonly subject?: string },
|
|
102
|
-
): Promise<string> {
|
|
103
|
-
const issuer = opts?.issuer ?? DEFAULT_MANAGER_ISSUER
|
|
104
|
-
const audience = opts?.audience ?? issuer
|
|
105
|
-
const { privateJwk, kid } = await loadSigningMaterial(subject, keysDir)
|
|
106
|
-
return signIdentityCredential({
|
|
107
|
-
privateJwk,
|
|
108
|
-
kid,
|
|
109
|
-
issuer,
|
|
110
|
-
subject: opts?.subject ?? subject,
|
|
111
|
-
audience,
|
|
112
|
-
})
|
|
113
|
-
}
|
package/src/keys/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
acceptKeypair,
|
|
3
|
-
fileExists,
|
|
4
|
-
generateEd25519Jwk,
|
|
5
|
-
importKeypair,
|
|
6
|
-
keypairPaths,
|
|
7
|
-
listIdentityKeys,
|
|
8
|
-
persistKeypair,
|
|
9
|
-
readKeypair,
|
|
10
|
-
removeKeypair,
|
|
11
|
-
} from './pair'
|
|
12
|
-
export { loadAuth, persistAuth, resolveAuth, signAs } from './credential'
|
|
13
|
-
export type { Keypair, KeypairInput, KeypairPaths } from './pair'
|
|
14
|
-
export type { AuthBinding } from './credential'
|
package/src/keys/pair.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { compactVerify, CompactSign, exportJWK, generateKeyPair, importJWK, type JWK } from 'jose'
|
|
2
|
-
import { randomUUID } from 'node:crypto'
|
|
3
|
-
import { access, mkdir, readFile, unlink } from 'node:fs/promises'
|
|
4
|
-
import { dirname, join, resolve } from 'node:path'
|
|
5
|
-
|
|
6
|
-
import { AstraleError } from '../errors'
|
|
7
|
-
import { atomicWrite, KEYS_DIR } from '../state/index'
|
|
8
|
-
import { inferAlg } from './algorithm'
|
|
9
|
-
|
|
10
|
-
const LEGACY_MANAGER_PRIVATE = 'manager.private.jwk'
|
|
11
|
-
const LEGACY_MANAGER_PUBLIC = 'manager.public.jwk'
|
|
12
|
-
|
|
13
|
-
export type KeypairPaths = {
|
|
14
|
-
readonly privatePath: string
|
|
15
|
-
readonly publicPath: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface Keypair {
|
|
19
|
-
readonly privateJwk: JWK
|
|
20
|
-
readonly publicJwk: JWK
|
|
21
|
-
readonly kid?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface KeypairInput {
|
|
25
|
-
readonly privateJwk: unknown
|
|
26
|
-
readonly publicJwk: unknown
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Preserve manager filenames and confine every subject file to the selected key directory. */
|
|
30
|
-
export function keypairPaths(subject: string, keysDir: string = KEYS_DIR): KeypairPaths {
|
|
31
|
-
if (
|
|
32
|
-
subject.length === 0 ||
|
|
33
|
-
subject.includes('\0') ||
|
|
34
|
-
subject.includes('/') ||
|
|
35
|
-
subject.includes('\\')
|
|
36
|
-
) {
|
|
37
|
-
throw invalidKeySubject(subject)
|
|
38
|
-
}
|
|
39
|
-
const filenames =
|
|
40
|
-
subject === 'manager'
|
|
41
|
-
? { private: LEGACY_MANAGER_PRIVATE, public: LEGACY_MANAGER_PUBLIC }
|
|
42
|
-
: { private: `${subject}.private.jwk`, public: `${subject}.public.jwk` }
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
privatePath: confinedKeyPath(keysDir, filenames.private, subject),
|
|
46
|
-
publicPath: confinedKeyPath(keysDir, filenames.public, subject),
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function confinedKeyPath(keysDir: string, filename: string, subject: string): string {
|
|
51
|
-
const path = join(keysDir, filename)
|
|
52
|
-
if (dirname(resolve(path)) !== resolve(keysDir)) throw invalidKeySubject(subject)
|
|
53
|
-
return path
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function invalidKeySubject(subject: string): AstraleError {
|
|
57
|
-
return new AstraleError(
|
|
58
|
-
'INVALID_KEY_SUBJECT',
|
|
59
|
-
`Identity subject ${JSON.stringify(subject)} cannot name a key file.`,
|
|
60
|
-
'Use a non-empty subject without path separators.',
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function invalidKeypair(message: string): AstraleError {
|
|
65
|
-
return new AstraleError('INVALID_KEYPAIR', message, 'Import a supported matching keypair.')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function acceptJwk(input: unknown, role: 'private' | 'public'): JWK {
|
|
69
|
-
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
70
|
-
throw invalidKeypair(`The ${role} JWK must be an object.`)
|
|
71
|
-
}
|
|
72
|
-
const jwk = { ...input } as JWK
|
|
73
|
-
if (jwk.kid !== undefined && (typeof jwk.kid !== 'string' || jwk.kid.length === 0)) {
|
|
74
|
-
throw invalidKeypair(`The ${role} JWK kid must be a non-empty string when present.`)
|
|
75
|
-
}
|
|
76
|
-
return jwk
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function acceptKeypair(input: KeypairInput): Promise<Keypair> {
|
|
80
|
-
const privateJwk = acceptJwk(input.privateJwk, 'private')
|
|
81
|
-
const publicJwk = acceptJwk(input.publicJwk, 'public')
|
|
82
|
-
if (!Object.hasOwn(privateJwk, 'd')) {
|
|
83
|
-
throw invalidKeypair('The private JWK does not contain private key material.')
|
|
84
|
-
}
|
|
85
|
-
if (Object.hasOwn(publicJwk, 'd')) {
|
|
86
|
-
throw invalidKeypair('The public JWK must not contain private key material.')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const privateAlg = inferAlg(privateJwk as Record<string, unknown>)
|
|
90
|
-
const publicAlg = inferAlg(publicJwk as Record<string, unknown>)
|
|
91
|
-
if (privateAlg !== publicAlg) {
|
|
92
|
-
throw invalidKeypair(
|
|
93
|
-
`The private and public JWK algorithms differ (${privateAlg} versus ${publicAlg}).`,
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
if (privateJwk.kid && publicJwk.kid && privateJwk.kid !== publicJwk.kid) {
|
|
97
|
-
throw invalidKeypair('The private and public JWK key IDs differ.')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const [privateKey, publicKey] = await Promise.all([
|
|
102
|
-
importJWK(privateJwk, privateAlg),
|
|
103
|
-
importJWK(publicJwk, publicAlg),
|
|
104
|
-
])
|
|
105
|
-
const proof = await new CompactSign(new TextEncoder().encode('astrale-keypair-proof'))
|
|
106
|
-
.setProtectedHeader({ alg: privateAlg })
|
|
107
|
-
.sign(privateKey)
|
|
108
|
-
await compactVerify(proof, publicKey, { algorithms: [publicAlg] })
|
|
109
|
-
} catch {
|
|
110
|
-
throw invalidKeypair('The private and public JWKs do not form a valid matching pair.')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return { privateJwk, publicJwk, kid: privateJwk.kid ?? publicJwk.kid }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export async function fileExists(path: string): Promise<boolean> {
|
|
117
|
-
try {
|
|
118
|
-
await access(path)
|
|
119
|
-
return true
|
|
120
|
-
} catch (error) {
|
|
121
|
-
if ((error as { code?: string }).code === 'ENOENT') return false
|
|
122
|
-
throw error
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export async function listIdentityKeys(keysDir: string = KEYS_DIR): Promise<string[]> {
|
|
127
|
-
try {
|
|
128
|
-
const { readdir } = await import('node:fs/promises')
|
|
129
|
-
const entries = await readdir(keysDir)
|
|
130
|
-
const names = new Set<string>()
|
|
131
|
-
for (const entry of entries) {
|
|
132
|
-
if (entry === LEGACY_MANAGER_PRIVATE) names.add('manager')
|
|
133
|
-
else if (entry.endsWith('.private.jwk')) names.add(entry.replace(/\.private\.jwk$/, ''))
|
|
134
|
-
}
|
|
135
|
-
return Array.from(names).sort()
|
|
136
|
-
} catch (error) {
|
|
137
|
-
if ((error as { code?: string }).code === 'ENOENT') return []
|
|
138
|
-
throw error
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export async function generateEd25519Jwk(
|
|
143
|
-
kid: string,
|
|
144
|
-
): Promise<{ readonly privateJwk: JWK; readonly publicJwk: JWK }> {
|
|
145
|
-
const { publicKey, privateKey } = await generateKeyPair('EdDSA', {
|
|
146
|
-
crv: 'Ed25519',
|
|
147
|
-
extractable: true,
|
|
148
|
-
})
|
|
149
|
-
const privateJwk = await exportJWK(privateKey)
|
|
150
|
-
const publicJwk = await exportJWK(publicKey)
|
|
151
|
-
privateJwk.alg = 'EdDSA'
|
|
152
|
-
publicJwk.alg = 'EdDSA'
|
|
153
|
-
privateJwk.kid = kid
|
|
154
|
-
publicJwk.kid = kid
|
|
155
|
-
return { privateJwk, publicJwk }
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export async function persistKeypair(
|
|
159
|
-
subject: string,
|
|
160
|
-
opts?: { readonly keysDir?: string; readonly kid?: string },
|
|
161
|
-
): Promise<{ readonly publicJwk: JWK; readonly privateJwk: JWK; readonly kid: string }> {
|
|
162
|
-
const keysDir = opts?.keysDir ?? KEYS_DIR
|
|
163
|
-
const { publicKey, privateKey } = await generateKeyPair('ES256', { extractable: true })
|
|
164
|
-
const publicJwk = await exportJWK(publicKey)
|
|
165
|
-
const privateJwk = await exportJWK(privateKey)
|
|
166
|
-
const kid = opts?.kid ?? `${subject}-key-${randomUUID().slice(0, 8)}`
|
|
167
|
-
publicJwk.kid = kid
|
|
168
|
-
privateJwk.kid = kid
|
|
169
|
-
publicJwk.alg = 'ES256'
|
|
170
|
-
privateJwk.alg = 'ES256'
|
|
171
|
-
|
|
172
|
-
await writeKeypair(subject, { privateJwk, publicJwk, kid }, keysDir)
|
|
173
|
-
return { publicJwk, privateJwk, kid }
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export async function readKeypair(subject: string, keysDir: string = KEYS_DIR): Promise<Keypair> {
|
|
177
|
-
const { privatePath, publicPath } = keypairPaths(subject, keysDir)
|
|
178
|
-
try {
|
|
179
|
-
const [privateRaw, publicRaw] = await Promise.all([
|
|
180
|
-
readFile(privatePath, 'utf-8'),
|
|
181
|
-
readFile(publicPath, 'utf-8'),
|
|
182
|
-
])
|
|
183
|
-
return await acceptKeypair({
|
|
184
|
-
privateJwk: JSON.parse(privateRaw) as unknown,
|
|
185
|
-
publicJwk: JSON.parse(publicRaw) as unknown,
|
|
186
|
-
})
|
|
187
|
-
} catch (error) {
|
|
188
|
-
if (error instanceof AstraleError) throw error
|
|
189
|
-
throw invalidKeypair(`Could not read the keypair for identity ${JSON.stringify(subject)}.`)
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export async function importKeypair(
|
|
194
|
-
subject: string,
|
|
195
|
-
input: KeypairInput,
|
|
196
|
-
keysDir: string = KEYS_DIR,
|
|
197
|
-
): Promise<Keypair> {
|
|
198
|
-
const pair = await acceptKeypair(input)
|
|
199
|
-
await writeKeypair(subject, pair, keysDir)
|
|
200
|
-
return pair
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async function writeKeypair(subject: string, pair: Keypair, keysDir: string): Promise<void> {
|
|
204
|
-
const { privatePath, publicPath } = keypairPaths(subject, keysDir)
|
|
205
|
-
await mkdir(keysDir, { recursive: true })
|
|
206
|
-
await atomicWrite(privatePath, JSON.stringify(pair.privateJwk, null, 2))
|
|
207
|
-
await atomicWrite(publicPath, JSON.stringify(pair.publicJwk, null, 2))
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export async function removeKeypair(subject: string, keysDir: string = KEYS_DIR): Promise<void> {
|
|
211
|
-
const { privatePath, publicPath } = keypairPaths(subject, keysDir)
|
|
212
|
-
for (const path of [privatePath, publicPath]) {
|
|
213
|
-
try {
|
|
214
|
-
await unlink(path)
|
|
215
|
-
} catch (error) {
|
|
216
|
-
if ((error as { code?: string }).code !== 'ENOENT') throw error
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|