@p4code/cli 0.0.40 → 0.0.41
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 +18 -18
- package/dist/bin.mjs +1346 -1157
- package/dist/client/assets/{DiffPanel-CWTAOHXy.js → DiffPanel-D8nVXZ0Y.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-CMon_ZW_.js → FilePreviewPanel-CFKCFyJ3.js} +2 -2
- package/dist/client/assets/{PreviewPanel-BbkJhucn.js → PreviewPanel-VQey9906.js} +2 -2
- package/dist/client/assets/{angular-html-eRIeZaHm.js → angular-html-D2LtifxG.js} +2 -2
- package/dist/client/assets/{angular-ts-CSJI6jVI.js → angular-ts-oVDpKJb2.js} +2 -2
- package/dist/client/assets/{apl-N_WYRnOX.js → apl-2vnm7rdp.js} +2 -2
- package/dist/client/assets/arrow-right-DmJqM7J1.js +2 -0
- package/dist/client/assets/{astro-YwKix1hR.js → astro-CDbEfI_f.js} +2 -2
- package/dist/client/assets/{blade-BvX6Aco4.js → blade-B1MwvSO0.js} +2 -2
- package/dist/client/assets/{c-DzTwtKJT.js → c-Bi5eHJiI.js} +2 -2
- package/dist/client/assets/{cobol-C-OXLFvN.js → cobol-BxESWi7D.js} +2 -2
- package/dist/client/assets/{coffee-DZE-1kG8.js → coffee-s151wUsp.js} +2 -2
- package/dist/client/assets/{cpp-CeJP0fjF.js → cpp-B51xjAkw.js} +2 -2
- package/dist/client/assets/{crystal-ZNMNBNdv.js → crystal-BW0dq2Rw.js} +2 -2
- package/dist/client/assets/{css-MuTAnxi-.js → css-Czn5ycv4.js} +2 -2
- package/dist/client/assets/{edge-L_Gi4x7J.js → edge-DPtVbjt1.js} +2 -2
- package/dist/client/assets/{elixir-CY-skSPd.js → elixir-381Dy51E.js} +2 -2
- package/dist/client/assets/{elm-BOr95y4k.js → elm-Bpu_ckb9.js} +2 -2
- package/dist/client/assets/{erb-oPVsfoEW.js → erb-u1vpMXOw.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-B5fswj6j.js → fileCommentAnnotations-BwnrRpfq.js} +2 -2
- package/dist/client/assets/{git-rebase-DCP3SOJt.js → git-rebase-ShzdQ6mA.js} +2 -2
- package/dist/client/assets/{glimmer-js-ocvtKWV0.js → glimmer-js-DI4_LeGA.js} +2 -2
- package/dist/client/assets/{glimmer-ts-D9NLnrn4.js → glimmer-ts-BrA1R4ia.js} +2 -2
- package/dist/client/assets/{glsl-B4zX2LmT.js → glsl--BAUZuR7.js} +2 -2
- package/dist/client/assets/{graphql-DiTBL-8c.js → graphql-XmtNe4tX.js} +2 -2
- package/dist/client/assets/{hack-akJz2jQV.js → hack-ZPcuqt6I.js} +2 -2
- package/dist/client/assets/{haml-BVeQWTnw.js → haml-BLmOYSAV.js} +2 -2
- package/dist/client/assets/{handlebars--grKNHAn.js → handlebars-BxR19vQc.js} +2 -2
- package/dist/client/assets/{html-B2pBdK0D.js → html-Do3TWS07.js} +2 -2
- package/dist/client/assets/{html-derivative-CFwZKVpu.js → html-derivative-CLJquIOq.js} +2 -2
- package/dist/client/assets/{http-DzxCdJrg.js → http-C6M7KGrt.js} +2 -2
- package/dist/client/assets/{hurl-kzM3zOis.js → hurl-AFYDoSjV.js} +2 -2
- package/dist/client/assets/index-XxxL_T8f.js +635 -0
- package/dist/client/assets/{java-BC2ZGkYM.js → java-DoRNyNZ_.js} +2 -2
- package/dist/client/assets/{javascript-gqBOm55O.js → javascript-Dd-zEQJs.js} +2 -2
- package/dist/client/assets/{jinja-nVgqZ5uh.js → jinja-TJRtEusk.js} +2 -2
- package/dist/client/assets/{jison-Bvv8hysr.js → jison-bG9mcEJe.js} +2 -2
- package/dist/client/assets/{json-CLQfEwQv.js → json-DY6EaQUk.js} +2 -2
- package/dist/client/assets/{jsx-Bhahz-OT.js → jsx-BuL0_m-Z.js} +2 -2
- package/dist/client/assets/{julia-DJU1mzIk.js → julia-4bcuyKPV.js} +2 -2
- package/dist/client/assets/{just-XGKk4v1o.js → just-F3UNctxi.js} +2 -2
- package/dist/client/assets/{latex-CJcPMeCn.js → latex-DCJY-Aag.js} +2 -2
- package/dist/client/assets/{liquid-EuLmnfV7.js → liquid-M906bGYJ.js} +2 -2
- package/dist/client/assets/{lua-B9R161Tp.js → lua-CeupCtSb.js} +2 -2
- package/dist/client/assets/{marko-CB03L6Sz.js → marko-D_3SsXZn.js} +2 -2
- package/dist/client/assets/{mdc-Ccr4X0uO.js → mdc-DRUaGPVE.js} +2 -2
- package/dist/client/assets/{nginx-BS3I1QaZ.js → nginx-B_DJSFb0.js} +2 -2
- package/dist/client/assets/{nim-D7Zyd_8R.js → nim-BxoRFkNd.js} +2 -2
- package/dist/client/assets/{perl-e48m28fL.js → perl-CjLiNsGL.js} +2 -2
- package/dist/client/assets/{php-ocRBBnGb.js → php-DcklQON0.js} +2 -2
- package/dist/client/assets/{pug-BBs8wX-p.js → pug-DHYHUkbl.js} +2 -2
- package/dist/client/assets/{qml-Dwjp2Qzl.js → qml-Cggn-sFR.js} +2 -2
- package/dist/client/assets/{r-DF1H1luy.js → r-BhkOaKoM.js} +2 -2
- package/dist/client/assets/{razor-U_WjoOo_.js → razor-DksRt091.js} +2 -2
- package/dist/client/assets/{regexp-DGgHOmJp.js → regexp-CVeVJKpO.js} +2 -2
- package/dist/client/assets/{rst-BUd3Iwh2.js → rst-B8gnyB7p.js} +2 -2
- package/dist/client/assets/{ruby-CXnn06qc.js → ruby-B7aYI7EY.js} +2 -2
- package/dist/client/assets/{sas-BWPra-UJ.js → sas-DmDsVHsr.js} +2 -2
- package/dist/client/assets/{scss-YWDt-8TA.js → scss-Cf0hFbmP.js} +2 -2
- package/dist/client/assets/{shellscript-DNaMiy_8.js → shellscript-CJ2l7Ljo.js} +2 -2
- package/dist/client/assets/{shellsession-4u8MjHqU.js → shellsession-CE-6mg0T.js} +2 -2
- package/dist/client/assets/{soy-DjtwNNsb.js → soy-eTwUOhLL.js} +2 -2
- package/dist/client/assets/{sql-Dn7Q7_bz.js → sql-DaTGJ1E1.js} +2 -2
- package/dist/client/assets/{stata-B9KaDIK8.js → stata-DCqqmU-V.js} +2 -2
- package/dist/client/assets/{surrealql-C8pR4xmG.js → surrealql-DJuyQLb6.js} +2 -2
- package/dist/client/assets/{svelte-BsaIxqiC.js → svelte-DZfElFr5.js} +2 -2
- package/dist/client/assets/{templ-CzR4BK3F.js → templ-BeuT7j1S.js} +2 -2
- package/dist/client/assets/{tex-G7GUVjb0.js → tex-CUjxQ3iG.js} +2 -2
- package/dist/client/assets/textarea-BM2z9WNB.js +46 -0
- package/dist/client/assets/{ts-tags-5xxlNsGf.js → ts-tags-C00W--Ve.js} +2 -2
- package/dist/client/assets/{tsx-DVYZm60Q.js → tsx-h-kXP2gA.js} +2 -2
- package/dist/client/assets/{twig-G0p9qB4O.js → twig-DhMoAQY-.js} +2 -2
- package/dist/client/assets/{typescript-D8l0OAzJ.js → typescript-DwJdYtiC.js} +2 -2
- package/dist/client/assets/{vue-Bd-pqwQF.js → vue-CvLnzHbi.js} +2 -2
- package/dist/client/assets/{vue-html-CJEcN7mo.js → vue-html-fj2AgeR4.js} +2 -2
- package/dist/client/assets/{vue-vine-D-v2YHMk.js → vue-vine-D1-Lx9ZR.js} +2 -2
- package/dist/client/assets/{xml-JS10jM3R.js → xml-CDkXSKu_.js} +2 -2
- package/dist/client/assets/{xsl-BsXeGtCx.js → xsl-BkotjMT0.js} +2 -2
- package/dist/client/assets/{yaml-DUaQ_QeW.js → yaml-BssI7mmn.js} +2 -2
- package/dist/client/index.html +2 -2
- package/package.json +2 -2
- package/dist/client/assets/arrow-right-CfIjGfWz.js +0 -2
- package/dist/client/assets/index-C-C3Iio5.js +0 -635
- package/dist/client/assets/textarea-BJwGmoQB.js +0 -46
package/dist/bin.mjs
CHANGED
|
@@ -7298,9 +7298,16 @@ const TaskToolError = Schema$1.Union([
|
|
|
7298
7298
|
*/
|
|
7299
7299
|
const AGENT_ASSET_NAME_MAX_LENGTH = 128;
|
|
7300
7300
|
const AGENT_ASSET_FILE_PATH_MAX_LENGTH = 256;
|
|
7301
|
-
/**
|
|
7301
|
+
/**
|
|
7302
|
+
* Per-file and whole-asset ceilings, enforced at the hub boundary.
|
|
7303
|
+
*
|
|
7304
|
+
* The byte total is the meaningful bound — it is what crosses the wire and what
|
|
7305
|
+
* a row costs. The file count is a backstop against something pathological, and
|
|
7306
|
+
* was initially set low enough to refuse real skills: a documentation-heavy one
|
|
7307
|
+
* with a reference directory passes 64 files without being remotely large.
|
|
7308
|
+
*/
|
|
7302
7309
|
const AGENT_ASSET_FILE_MAX_CHARS = 512 * 1024;
|
|
7303
|
-
const AGENT_ASSET_TOTAL_MAX_BYTES =
|
|
7310
|
+
const AGENT_ASSET_TOTAL_MAX_BYTES = 4 * 1024 * 1024;
|
|
7304
7311
|
/**
|
|
7305
7312
|
* What kind of thing this is.
|
|
7306
7313
|
*
|
|
@@ -7351,7 +7358,7 @@ const AgentAssetFile = Schema$1.Struct({
|
|
|
7351
7358
|
path: AgentAssetFilePath,
|
|
7352
7359
|
content: Schema$1.String.check(Schema$1.isMaxLength(AGENT_ASSET_FILE_MAX_CHARS))
|
|
7353
7360
|
});
|
|
7354
|
-
const AgentAssetFiles = Schema$1.Array(AgentAssetFile).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(
|
|
7361
|
+
const AgentAssetFiles = Schema$1.Array(AgentAssetFile).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(512));
|
|
7355
7362
|
/**
|
|
7356
7363
|
* A user-scope asset as the hub holds it.
|
|
7357
7364
|
*
|
|
@@ -7400,6 +7407,20 @@ const AgentAssetListFilter = Schema$1.Struct({ kind: Schema$1.optional(AgentAsse
|
|
|
7400
7407
|
* disconnecting.
|
|
7401
7408
|
*/
|
|
7402
7409
|
const HubSyncMode = Schema$1.Literals(["auto", "paused"]);
|
|
7410
|
+
/**
|
|
7411
|
+
* Whether new local skills are shared automatically.
|
|
7412
|
+
*
|
|
7413
|
+
* `all` by default, because the hub exists so that a skill added on one machine
|
|
7414
|
+
* does not change agent behaviour there and nowhere else — and a share you have
|
|
7415
|
+
* to remember to perform is one that silently stops happening. `selected` is
|
|
7416
|
+
* for a machine that holds skills which should stay put.
|
|
7417
|
+
*
|
|
7418
|
+
* Note what this does not change: nothing is ever taken over. A name the hub
|
|
7419
|
+
* already holds from another machine is reported as a collision rather than
|
|
7420
|
+
* overwritten, whichever mode is set.
|
|
7421
|
+
*/
|
|
7422
|
+
const HubShareMode = Schema$1.Literals(["all", "selected"]);
|
|
7423
|
+
const HubShareModeInput = Schema$1.Struct({ mode: HubShareMode });
|
|
7403
7424
|
/** Where a server's hub link came from, so the UI can say why it is read-only. */
|
|
7404
7425
|
const HubLinkSource = Schema$1.Literals([
|
|
7405
7426
|
"environment",
|
|
@@ -7440,6 +7461,7 @@ const SkillSyncStatus = Schema$1.Struct({
|
|
|
7440
7461
|
configured: Schema$1.Boolean,
|
|
7441
7462
|
hubUrl: Schema$1.NullOr(Schema$1.String),
|
|
7442
7463
|
syncMode: HubSyncMode,
|
|
7464
|
+
shareMode: HubShareMode,
|
|
7443
7465
|
source: HubLinkSource,
|
|
7444
7466
|
skillsRoot: Schema$1.NullOr(Schema$1.String),
|
|
7445
7467
|
entries: Schema$1.Array(SkillSyncEntry),
|
|
@@ -7749,9 +7771,11 @@ const WS_METHODS = {
|
|
|
7749
7771
|
hubConnect: "hub.connect",
|
|
7750
7772
|
hubDisconnect: "hub.disconnect",
|
|
7751
7773
|
hubSetSyncMode: "hub.setSyncMode",
|
|
7774
|
+
hubSetShareMode: "hub.setShareMode",
|
|
7752
7775
|
hubMintToken: "hub.mintToken",
|
|
7753
7776
|
skillsSync: "skills.sync",
|
|
7754
7777
|
skillsPublish: "skills.publish",
|
|
7778
|
+
skillsPublishAll: "skills.publishAll",
|
|
7755
7779
|
skillsUnpublish: "skills.unpublish",
|
|
7756
7780
|
sourceControlLookupRepository: "sourceControl.lookupRepository",
|
|
7757
7781
|
sourceControlCloneRepository: "sourceControl.cloneRepository",
|
|
@@ -7849,6 +7873,11 @@ const WsHubSetSyncModeRpc = Rpc.make(WS_METHODS.hubSetSyncMode, {
|
|
|
7849
7873
|
* has no `crypto.subtle`, and a token generator that silently degrades is not
|
|
7850
7874
|
* a thing to ship.
|
|
7851
7875
|
*/
|
|
7876
|
+
const WsHubSetShareModeRpc = Rpc.make(WS_METHODS.hubSetShareMode, {
|
|
7877
|
+
payload: HubShareModeInput,
|
|
7878
|
+
success: SkillSyncStatus,
|
|
7879
|
+
error: HubRpcError
|
|
7880
|
+
});
|
|
7852
7881
|
const WsHubMintTokenRpc = Rpc.make(WS_METHODS.hubMintToken, {
|
|
7853
7882
|
payload: Schema$1.Struct({}),
|
|
7854
7883
|
success: HubMintedToken,
|
|
@@ -7870,6 +7899,14 @@ const WsSkillsPublishRpc = Rpc.make(WS_METHODS.skillsPublish, {
|
|
|
7870
7899
|
}),
|
|
7871
7900
|
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
7872
7901
|
});
|
|
7902
|
+
const WsSkillsPublishAllRpc = Rpc.make(WS_METHODS.skillsPublishAll, {
|
|
7903
|
+
payload: Schema$1.Struct({}),
|
|
7904
|
+
success: Schema$1.Struct({
|
|
7905
|
+
report: SkillSyncReport,
|
|
7906
|
+
status: SkillSyncStatus
|
|
7907
|
+
}),
|
|
7908
|
+
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
7909
|
+
});
|
|
7873
7910
|
const WsSkillsUnpublishRpc = Rpc.make(WS_METHODS.skillsUnpublish, {
|
|
7874
7911
|
payload: SkillNameInput,
|
|
7875
7912
|
success: Schema$1.Struct({
|
|
@@ -8246,7 +8283,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
|
|
|
8246
8283
|
error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
|
|
8247
8284
|
stream: true
|
|
8248
8285
|
});
|
|
8249
|
-
const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsUnpublishRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
|
|
8286
|
+
const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
|
|
8250
8287
|
//#endregion
|
|
8251
8288
|
//#region ../../packages/shared/src/oauthScope.ts
|
|
8252
8289
|
const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
|
|
@@ -8481,58 +8518,109 @@ Schema$1.TaggedErrorClass()("ProviderSessionRepositoryPersistenceError", {
|
|
|
8481
8518
|
cause: Schema$1.optional(Schema$1.Defect())
|
|
8482
8519
|
});
|
|
8483
8520
|
//#endregion
|
|
8484
|
-
//#region src/persistence/
|
|
8485
|
-
const
|
|
8486
|
-
id: Schema$1.String,
|
|
8487
|
-
credential: Schema$1.String,
|
|
8488
|
-
method: Schema$1.Literals(["desktop-bootstrap", "one-time-token"]),
|
|
8489
|
-
scopes: Schema$1.fromJsonString(AuthEnvironmentScopes),
|
|
8490
|
-
subject: Schema$1.String,
|
|
8521
|
+
//#region src/persistence/AuthSessions.ts
|
|
8522
|
+
const AuthSessionClientMetadataRecord = Schema$1.Struct({
|
|
8491
8523
|
label: Schema$1.NullOr(Schema$1.String),
|
|
8492
|
-
|
|
8493
|
-
|
|
8524
|
+
ipAddress: Schema$1.NullOr(Schema$1.String),
|
|
8525
|
+
userAgent: Schema$1.NullOr(Schema$1.String),
|
|
8526
|
+
deviceType: AuthClientMetadataDeviceType,
|
|
8527
|
+
os: Schema$1.NullOr(Schema$1.String),
|
|
8528
|
+
browser: Schema$1.NullOr(Schema$1.String)
|
|
8529
|
+
});
|
|
8530
|
+
Schema$1.Struct({
|
|
8531
|
+
sessionId: AuthSessionId,
|
|
8532
|
+
subject: Schema$1.String,
|
|
8533
|
+
scopes: AuthEnvironmentScopes,
|
|
8534
|
+
method: ServerAuthSessionMethod,
|
|
8535
|
+
client: AuthSessionClientMetadataRecord,
|
|
8536
|
+
issuedAt: Schema$1.DateTimeUtcFromString,
|
|
8494
8537
|
expiresAt: Schema$1.DateTimeUtcFromString,
|
|
8495
|
-
|
|
8538
|
+
lastConnectedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
|
|
8496
8539
|
revokedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString)
|
|
8497
8540
|
});
|
|
8498
|
-
const
|
|
8499
|
-
|
|
8500
|
-
credential: Schema$1.String,
|
|
8501
|
-
method: Schema$1.Literals(["desktop-bootstrap", "one-time-token"]),
|
|
8502
|
-
scopes: AuthEnvironmentScopes,
|
|
8541
|
+
const CreateAuthSessionInput = Schema$1.Struct({
|
|
8542
|
+
sessionId: AuthSessionId,
|
|
8503
8543
|
subject: Schema$1.String,
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8544
|
+
scopes: AuthEnvironmentScopes,
|
|
8545
|
+
method: ServerAuthSessionMethod,
|
|
8546
|
+
client: AuthSessionClientMetadataRecord,
|
|
8547
|
+
issuedAt: Schema$1.DateTimeUtcFromString,
|
|
8507
8548
|
expiresAt: Schema$1.DateTimeUtcFromString
|
|
8508
8549
|
});
|
|
8509
|
-
const
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8550
|
+
const GetAuthSessionByIdInput = Schema$1.Struct({ sessionId: AuthSessionId });
|
|
8551
|
+
const ListActiveAuthSessionsInput = Schema$1.Struct({ now: Schema$1.DateTimeUtcFromString });
|
|
8552
|
+
const RevokeAuthSessionInput = Schema$1.Struct({
|
|
8553
|
+
sessionId: AuthSessionId,
|
|
8554
|
+
revokedAt: Schema$1.DateTimeUtcFromString
|
|
8514
8555
|
});
|
|
8515
|
-
const
|
|
8516
|
-
|
|
8517
|
-
id: Schema$1.String,
|
|
8556
|
+
const RevokeOtherAuthSessionsInput = Schema$1.Struct({
|
|
8557
|
+
currentSessionId: AuthSessionId,
|
|
8518
8558
|
revokedAt: Schema$1.DateTimeUtcFromString
|
|
8519
8559
|
});
|
|
8520
|
-
const
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8560
|
+
const SetAuthSessionLastConnectedAtInput = Schema$1.Struct({
|
|
8561
|
+
sessionId: AuthSessionId,
|
|
8562
|
+
lastConnectedAt: Schema$1.DateTimeUtcFromString
|
|
8563
|
+
});
|
|
8564
|
+
var AuthSessionRepository = class extends Context.Service()("@p4code/cli/persistence/AuthSessions/AuthSessionRepository") {};
|
|
8565
|
+
const AuthSessionDbRow = Schema$1.Struct({
|
|
8566
|
+
sessionId: AuthSessionId,
|
|
8567
|
+
subject: Schema$1.String,
|
|
8568
|
+
scopes: Schema$1.fromJsonString(AuthEnvironmentScopes),
|
|
8569
|
+
method: ServerAuthSessionMethod,
|
|
8570
|
+
clientLabel: Schema$1.NullOr(Schema$1.String),
|
|
8571
|
+
clientIpAddress: Schema$1.NullOr(Schema$1.String),
|
|
8572
|
+
clientUserAgent: Schema$1.NullOr(Schema$1.String),
|
|
8573
|
+
clientDeviceType: Schema$1.Literals([
|
|
8574
|
+
"desktop",
|
|
8575
|
+
"mobile",
|
|
8576
|
+
"tablet",
|
|
8577
|
+
"bot",
|
|
8578
|
+
"unknown"
|
|
8579
|
+
]),
|
|
8580
|
+
clientOs: Schema$1.NullOr(Schema$1.String),
|
|
8581
|
+
clientBrowser: Schema$1.NullOr(Schema$1.String),
|
|
8582
|
+
issuedAt: Schema$1.DateTimeUtcFromString,
|
|
8583
|
+
expiresAt: Schema$1.DateTimeUtcFromString,
|
|
8584
|
+
lastConnectedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
|
|
8585
|
+
revokedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString)
|
|
8586
|
+
});
|
|
8587
|
+
const AuthSessionRawDbRow = Schema$1.Struct({
|
|
8588
|
+
sessionId: Schema$1.String,
|
|
8526
8589
|
subject: Schema$1.Unknown,
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8590
|
+
scopes: Schema$1.Unknown,
|
|
8591
|
+
method: Schema$1.Unknown,
|
|
8592
|
+
clientLabel: Schema$1.Unknown,
|
|
8593
|
+
clientIpAddress: Schema$1.Unknown,
|
|
8594
|
+
clientUserAgent: Schema$1.Unknown,
|
|
8595
|
+
clientDeviceType: Schema$1.Unknown,
|
|
8596
|
+
clientOs: Schema$1.Unknown,
|
|
8597
|
+
clientBrowser: Schema$1.Unknown,
|
|
8598
|
+
issuedAt: Schema$1.Unknown,
|
|
8530
8599
|
expiresAt: Schema$1.Unknown,
|
|
8531
|
-
|
|
8600
|
+
lastConnectedAt: Schema$1.Unknown,
|
|
8532
8601
|
revokedAt: Schema$1.Unknown
|
|
8533
8602
|
});
|
|
8534
|
-
const
|
|
8535
|
-
|
|
8603
|
+
const decodeAuthSessionDbRow = Schema$1.decodeUnknownEffect(AuthSessionDbRow);
|
|
8604
|
+
function toAuthSessionRecord(row) {
|
|
8605
|
+
return {
|
|
8606
|
+
sessionId: row.sessionId,
|
|
8607
|
+
subject: row.subject,
|
|
8608
|
+
scopes: row.scopes,
|
|
8609
|
+
method: row.method,
|
|
8610
|
+
client: {
|
|
8611
|
+
label: row.clientLabel,
|
|
8612
|
+
ipAddress: row.clientIpAddress,
|
|
8613
|
+
userAgent: row.clientUserAgent,
|
|
8614
|
+
deviceType: row.clientDeviceType,
|
|
8615
|
+
os: row.clientOs,
|
|
8616
|
+
browser: row.clientBrowser
|
|
8617
|
+
},
|
|
8618
|
+
issuedAt: row.issuedAt,
|
|
8619
|
+
expiresAt: row.expiresAt,
|
|
8620
|
+
lastConnectedAt: row.lastConnectedAt,
|
|
8621
|
+
revokedAt: row.revokedAt
|
|
8622
|
+
};
|
|
8623
|
+
}
|
|
8536
8624
|
function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlation) {
|
|
8537
8625
|
return (cause) => Schema$1.isSchemaError(cause) ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause, correlation) : new PersistenceSqlError({
|
|
8538
8626
|
operation: sqlOperation,
|
|
@@ -8542,674 +8630,881 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
|
|
|
8542
8630
|
}
|
|
8543
8631
|
const make$67 = Effect.gen(function* () {
|
|
8544
8632
|
const sql = yield* SqlClient.SqlClient;
|
|
8545
|
-
const
|
|
8546
|
-
Request:
|
|
8633
|
+
const createSessionRow = SqlSchema.void({
|
|
8634
|
+
Request: CreateAuthSessionInput,
|
|
8547
8635
|
execute: (input) => sql`
|
|
8548
|
-
INSERT INTO
|
|
8549
|
-
|
|
8550
|
-
credential,
|
|
8551
|
-
method,
|
|
8552
|
-
scopes,
|
|
8636
|
+
INSERT INTO auth_sessions (
|
|
8637
|
+
session_id,
|
|
8553
8638
|
subject,
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8639
|
+
scopes,
|
|
8640
|
+
method,
|
|
8641
|
+
client_label,
|
|
8642
|
+
client_ip_address,
|
|
8643
|
+
client_user_agent,
|
|
8644
|
+
client_device_type,
|
|
8645
|
+
client_os,
|
|
8646
|
+
client_browser,
|
|
8647
|
+
issued_at,
|
|
8557
8648
|
expires_at,
|
|
8558
|
-
consumed_at,
|
|
8559
8649
|
revoked_at
|
|
8560
8650
|
)
|
|
8561
8651
|
VALUES (
|
|
8562
|
-
${input.
|
|
8563
|
-
${input.credential},
|
|
8564
|
-
${input.method},
|
|
8565
|
-
${JSON.stringify(input.scopes)},
|
|
8652
|
+
${input.sessionId},
|
|
8566
8653
|
${input.subject},
|
|
8567
|
-
${input.
|
|
8568
|
-
${input.
|
|
8569
|
-
${input.
|
|
8654
|
+
${JSON.stringify(input.scopes)},
|
|
8655
|
+
${input.method},
|
|
8656
|
+
${input.client.label},
|
|
8657
|
+
${input.client.ipAddress},
|
|
8658
|
+
${input.client.userAgent},
|
|
8659
|
+
${input.client.deviceType},
|
|
8660
|
+
${input.client.os},
|
|
8661
|
+
${input.client.browser},
|
|
8662
|
+
${input.issuedAt},
|
|
8570
8663
|
${input.expiresAt},
|
|
8571
|
-
NULL,
|
|
8572
8664
|
NULL
|
|
8573
8665
|
)
|
|
8574
8666
|
`
|
|
8575
8667
|
});
|
|
8576
|
-
const
|
|
8577
|
-
Request:
|
|
8578
|
-
Result:
|
|
8579
|
-
execute: ({
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
WHERE credential = ${credential}
|
|
8583
|
-
AND revoked_at IS NULL
|
|
8584
|
-
AND consumed_at IS NULL
|
|
8585
|
-
AND expires_at > ${now}
|
|
8586
|
-
AND (
|
|
8587
|
-
proof_key_thumbprint IS NULL
|
|
8588
|
-
OR proof_key_thumbprint = ${proofKeyThumbprint}
|
|
8589
|
-
)
|
|
8590
|
-
RETURNING
|
|
8591
|
-
id AS "id",
|
|
8592
|
-
credential AS "credential",
|
|
8593
|
-
method AS "method",
|
|
8594
|
-
scopes AS "scopes",
|
|
8668
|
+
const getSessionRowById = SqlSchema.findOneOption({
|
|
8669
|
+
Request: GetAuthSessionByIdInput,
|
|
8670
|
+
Result: AuthSessionRawDbRow,
|
|
8671
|
+
execute: ({ sessionId }) => sql`
|
|
8672
|
+
SELECT
|
|
8673
|
+
session_id AS "sessionId",
|
|
8595
8674
|
subject AS "subject",
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8675
|
+
scopes AS "scopes",
|
|
8676
|
+
method AS "method",
|
|
8677
|
+
client_label AS "clientLabel",
|
|
8678
|
+
client_ip_address AS "clientIpAddress",
|
|
8679
|
+
client_user_agent AS "clientUserAgent",
|
|
8680
|
+
client_device_type AS "clientDeviceType",
|
|
8681
|
+
client_os AS "clientOs",
|
|
8682
|
+
client_browser AS "clientBrowser",
|
|
8683
|
+
issued_at AS "issuedAt",
|
|
8599
8684
|
expires_at AS "expiresAt",
|
|
8600
|
-
|
|
8685
|
+
last_connected_at AS "lastConnectedAt",
|
|
8601
8686
|
revoked_at AS "revokedAt"
|
|
8687
|
+
FROM auth_sessions
|
|
8688
|
+
WHERE session_id = ${sessionId}
|
|
8602
8689
|
`
|
|
8603
8690
|
});
|
|
8604
|
-
const
|
|
8605
|
-
Request:
|
|
8606
|
-
Result:
|
|
8691
|
+
const listActiveSessionRows = SqlSchema.findAll({
|
|
8692
|
+
Request: ListActiveAuthSessionsInput,
|
|
8693
|
+
Result: AuthSessionRawDbRow,
|
|
8607
8694
|
execute: ({ now }) => sql`
|
|
8608
8695
|
SELECT
|
|
8609
|
-
|
|
8610
|
-
credential AS "credential",
|
|
8611
|
-
method AS "method",
|
|
8612
|
-
scopes AS "scopes",
|
|
8696
|
+
session_id AS "sessionId",
|
|
8613
8697
|
subject AS "subject",
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8698
|
+
scopes AS "scopes",
|
|
8699
|
+
method AS "method",
|
|
8700
|
+
client_label AS "clientLabel",
|
|
8701
|
+
client_ip_address AS "clientIpAddress",
|
|
8702
|
+
client_user_agent AS "clientUserAgent",
|
|
8703
|
+
client_device_type AS "clientDeviceType",
|
|
8704
|
+
client_os AS "clientOs",
|
|
8705
|
+
client_browser AS "clientBrowser",
|
|
8706
|
+
issued_at AS "issuedAt",
|
|
8617
8707
|
expires_at AS "expiresAt",
|
|
8618
|
-
|
|
8708
|
+
last_connected_at AS "lastConnectedAt",
|
|
8619
8709
|
revoked_at AS "revokedAt"
|
|
8620
|
-
FROM
|
|
8710
|
+
FROM auth_sessions
|
|
8621
8711
|
WHERE revoked_at IS NULL
|
|
8622
|
-
AND consumed_at IS NULL
|
|
8623
8712
|
AND expires_at > ${now}
|
|
8624
|
-
ORDER BY
|
|
8713
|
+
ORDER BY issued_at DESC, session_id DESC
|
|
8625
8714
|
`
|
|
8626
8715
|
});
|
|
8627
|
-
const
|
|
8628
|
-
Request:
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8716
|
+
const setLastConnectedAtRow = SqlSchema.void({
|
|
8717
|
+
Request: SetAuthSessionLastConnectedAtInput,
|
|
8718
|
+
execute: ({ sessionId, lastConnectedAt }) => sql`
|
|
8719
|
+
UPDATE auth_sessions
|
|
8720
|
+
SET last_connected_at = ${lastConnectedAt}
|
|
8721
|
+
WHERE session_id = ${sessionId}
|
|
8722
|
+
AND revoked_at IS NULL
|
|
8723
|
+
`
|
|
8724
|
+
});
|
|
8725
|
+
const revokeSessionRows = SqlSchema.findAll({
|
|
8726
|
+
Request: RevokeAuthSessionInput,
|
|
8727
|
+
Result: Schema$1.Struct({ sessionId: AuthSessionId }),
|
|
8728
|
+
execute: ({ sessionId, revokedAt }) => sql`
|
|
8729
|
+
UPDATE auth_sessions
|
|
8632
8730
|
SET revoked_at = ${revokedAt}
|
|
8633
|
-
WHERE
|
|
8731
|
+
WHERE session_id = ${sessionId}
|
|
8634
8732
|
AND revoked_at IS NULL
|
|
8635
|
-
|
|
8636
|
-
RETURNING id AS "id"
|
|
8733
|
+
RETURNING session_id AS "sessionId"
|
|
8637
8734
|
`
|
|
8638
8735
|
});
|
|
8639
|
-
const
|
|
8640
|
-
Request:
|
|
8641
|
-
Result:
|
|
8642
|
-
execute: ({
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
subject AS "subject",
|
|
8649
|
-
label AS "label",
|
|
8650
|
-
proof_key_thumbprint AS "proofKeyThumbprint",
|
|
8651
|
-
created_at AS "createdAt",
|
|
8652
|
-
expires_at AS "expiresAt",
|
|
8653
|
-
consumed_at AS "consumedAt",
|
|
8654
|
-
revoked_at AS "revokedAt"
|
|
8655
|
-
FROM auth_pairing_links
|
|
8656
|
-
WHERE credential = ${credential}
|
|
8736
|
+
const revokeOtherSessionRows = SqlSchema.findAll({
|
|
8737
|
+
Request: RevokeOtherAuthSessionsInput,
|
|
8738
|
+
Result: Schema$1.Struct({ sessionId: AuthSessionId }),
|
|
8739
|
+
execute: ({ currentSessionId, revokedAt }) => sql`
|
|
8740
|
+
UPDATE auth_sessions
|
|
8741
|
+
SET revoked_at = ${revokedAt}
|
|
8742
|
+
WHERE session_id <> ${currentSessionId}
|
|
8743
|
+
AND revoked_at IS NULL
|
|
8744
|
+
RETURNING session_id AS "sessionId"
|
|
8657
8745
|
`
|
|
8658
8746
|
});
|
|
8659
|
-
const create = (input) =>
|
|
8660
|
-
const
|
|
8747
|
+
const create = (input) => createSessionRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.create:query", "AuthSessionRepository.create:encodeRequest", { sessionId: input.sessionId })));
|
|
8748
|
+
const getById = (input) => getSessionRowById(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.getById:query", "AuthSessionRepository.getById:decodeRow", { sessionId: input.sessionId })), Effect.flatMap((rowOption) => Option.match(rowOption, {
|
|
8661
8749
|
onNone: () => Effect.succeed(Option.none()),
|
|
8662
|
-
onSome: (row) =>
|
|
8663
|
-
})));
|
|
8664
|
-
const listActive = (input) => listActivePairingLinkRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthPairingLinkRepository.listActive:query", "AuthPairingLinkRepository.listActive:decodeRows")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.listActive:decodeRows", cause, { pairingLinkId: row.id }))))));
|
|
8665
|
-
const revoke = (input) => revokePairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthPairingLinkRepository.revoke:query", "AuthPairingLinkRepository.revoke:decodeRows", { pairingLinkId: input.id })), Effect.map((rows) => rows.length > 0));
|
|
8666
|
-
const getByCredential = (input) => getPairingLinkRowByCredential(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthPairingLinkRepository.getByCredential:query", "AuthPairingLinkRepository.getByCredential:decodeRow")), Effect.flatMap((rowOption) => Option.match(rowOption, {
|
|
8667
|
-
onNone: () => Effect.succeed(Option.none()),
|
|
8668
|
-
onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.getByCredential:decodeRow", cause, { pairingLinkId: row.id })), Effect.map(Option.some))
|
|
8750
|
+
onSome: (row) => decodeAuthSessionDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthSessionRepository.getById:decodeRow", cause, { sessionId: input.sessionId })), Effect.map((decodedRow) => Option.some(toAuthSessionRecord(decodedRow))))
|
|
8669
8751
|
})));
|
|
8752
|
+
const listActive = (input) => listActiveSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.listActive:query", "AuthSessionRepository.listActive:decodeRows")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeAuthSessionDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthSessionRepository.listActive:decodeRows", cause, { sessionId: row.sessionId })), Effect.map(toAuthSessionRecord)))));
|
|
8753
|
+
const revoke = (input) => revokeSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.revoke:query", "AuthSessionRepository.revoke:decodeRows", { sessionId: input.sessionId })), Effect.map((rows) => rows.length > 0));
|
|
8754
|
+
const revokeAllExcept = (input) => revokeOtherSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.revokeAllExcept:query", "AuthSessionRepository.revokeAllExcept:decodeRows", { currentSessionId: input.currentSessionId })), Effect.map((rows) => rows.map((row) => row.sessionId)));
|
|
8755
|
+
const setLastConnectedAt = (input) => setLastConnectedAtRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.setLastConnectedAt:query", "AuthSessionRepository.setLastConnectedAt:encodeRequest", { sessionId: input.sessionId })));
|
|
8670
8756
|
return {
|
|
8671
8757
|
create,
|
|
8672
|
-
|
|
8758
|
+
getById,
|
|
8673
8759
|
listActive,
|
|
8674
8760
|
revoke,
|
|
8675
|
-
|
|
8761
|
+
revokeAllExcept,
|
|
8762
|
+
setLastConnectedAt
|
|
8676
8763
|
};
|
|
8677
8764
|
});
|
|
8678
|
-
const layer$63 = Layer.effect(
|
|
8765
|
+
const layer$63 = Layer.effect(AuthSessionRepository, make$67);
|
|
8679
8766
|
//#endregion
|
|
8680
|
-
//#region src/auth/
|
|
8681
|
-
|
|
8767
|
+
//#region src/auth/ServerSecretStore.ts
|
|
8768
|
+
const secretStoreErrorContext = {
|
|
8769
|
+
resource: Schema$1.String,
|
|
8770
|
+
cause: Schema$1.Defect()
|
|
8771
|
+
};
|
|
8772
|
+
var SecretStoreSecureError = class extends Schema$1.TaggedErrorClass()("SecretStoreSecureError", { ...secretStoreErrorContext }) {
|
|
8682
8773
|
get message() {
|
|
8683
|
-
return
|
|
8774
|
+
return `Failed to secure ${this.resource}.`;
|
|
8684
8775
|
}
|
|
8685
8776
|
};
|
|
8686
|
-
var
|
|
8777
|
+
var SecretStoreReadError = class extends Schema$1.TaggedErrorClass()("SecretStoreReadError", { ...secretStoreErrorContext }) {
|
|
8687
8778
|
get message() {
|
|
8688
|
-
return
|
|
8779
|
+
return `Failed to read ${this.resource}.`;
|
|
8689
8780
|
}
|
|
8690
8781
|
};
|
|
8691
|
-
var
|
|
8782
|
+
var SecretStoreTemporaryPathError = class extends Schema$1.TaggedErrorClass()("SecretStoreTemporaryPathError", { ...secretStoreErrorContext }) {
|
|
8692
8783
|
get message() {
|
|
8693
|
-
return
|
|
8784
|
+
return `Failed to create temporary path for ${this.resource}.`;
|
|
8694
8785
|
}
|
|
8695
8786
|
};
|
|
8696
|
-
var
|
|
8787
|
+
var SecretStorePersistError = class extends Schema$1.TaggedErrorClass()("SecretStorePersistError", { ...secretStoreErrorContext }) {
|
|
8697
8788
|
get message() {
|
|
8698
|
-
return
|
|
8789
|
+
return `Failed to persist ${this.resource}.`;
|
|
8699
8790
|
}
|
|
8700
8791
|
};
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8792
|
+
var SecretStoreRandomGenerationError = class extends Schema$1.TaggedErrorClass()("SecretStoreRandomGenerationError", { ...secretStoreErrorContext }) {
|
|
8793
|
+
get message() {
|
|
8794
|
+
return `Failed to generate random bytes for ${this.resource}.`;
|
|
8795
|
+
}
|
|
8796
|
+
};
|
|
8797
|
+
var SecretStoreConcurrentReadError = class extends Schema$1.TaggedErrorClass()("SecretStoreConcurrentReadError", { resource: Schema$1.String }) {
|
|
8798
|
+
get message() {
|
|
8799
|
+
return `Failed to read ${this.resource} after concurrent creation.`;
|
|
8800
|
+
}
|
|
8801
|
+
};
|
|
8802
|
+
var SecretStoreRemoveError = class extends Schema$1.TaggedErrorClass()("SecretStoreRemoveError", { ...secretStoreErrorContext }) {
|
|
8803
|
+
get message() {
|
|
8804
|
+
return `Failed to remove ${this.resource}.`;
|
|
8805
|
+
}
|
|
8806
|
+
};
|
|
8807
|
+
var SecretStoreDecodeError = class extends Schema$1.TaggedErrorClass()("SecretStoreDecodeError", { ...secretStoreErrorContext }) {
|
|
8808
|
+
get message() {
|
|
8809
|
+
return `Failed to decode ${this.resource}.`;
|
|
8810
|
+
}
|
|
8811
|
+
};
|
|
8812
|
+
var SecretStoreEncodeError = class extends Schema$1.TaggedErrorClass()("SecretStoreEncodeError", { ...secretStoreErrorContext }) {
|
|
8813
|
+
get message() {
|
|
8814
|
+
return `Failed to encode ${this.resource}.`;
|
|
8815
|
+
}
|
|
8816
|
+
};
|
|
8817
|
+
const SecretStoreError = Schema$1.Union([
|
|
8818
|
+
SecretStoreSecureError,
|
|
8819
|
+
SecretStoreReadError,
|
|
8820
|
+
SecretStoreTemporaryPathError,
|
|
8821
|
+
SecretStorePersistError,
|
|
8822
|
+
SecretStoreRandomGenerationError,
|
|
8823
|
+
SecretStoreConcurrentReadError,
|
|
8824
|
+
SecretStoreRemoveError,
|
|
8825
|
+
SecretStoreDecodeError,
|
|
8826
|
+
SecretStoreEncodeError
|
|
8706
8827
|
]);
|
|
8707
|
-
Schema$1.is(
|
|
8708
|
-
|
|
8828
|
+
const isSecretStoreError = Schema$1.is(SecretStoreError);
|
|
8829
|
+
const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
|
|
8830
|
+
const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
|
|
8831
|
+
var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
|
|
8832
|
+
const make$66 = Effect.gen(function* () {
|
|
8833
|
+
const crypto = yield* Crypto.Crypto;
|
|
8834
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
8835
|
+
const path = yield* Path.Path;
|
|
8836
|
+
const serverConfig = yield* ServerConfig$1;
|
|
8837
|
+
yield* fileSystem.makeDirectory(serverConfig.secretsDir, { recursive: true });
|
|
8838
|
+
yield* fileSystem.chmod(serverConfig.secretsDir, 448).pipe(Effect.mapError((cause) => new SecretStoreSecureError({
|
|
8839
|
+
resource: `secrets directory ${serverConfig.secretsDir}`,
|
|
8840
|
+
cause
|
|
8841
|
+
})));
|
|
8842
|
+
const resolveSecretPath = (name) => path.join(serverConfig.secretsDir, `${name}.bin`);
|
|
8843
|
+
const get = (name) => fileSystem.readFile(resolveSecretPath(name)).pipe(Effect.map((bytes) => Option.some(Uint8Array.from(bytes))), Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(new SecretStoreReadError({
|
|
8844
|
+
resource: `secret ${name}`,
|
|
8845
|
+
cause
|
|
8846
|
+
}))), Effect.withSpan("ServerSecretStore.get"));
|
|
8847
|
+
const set = (name, value) => {
|
|
8848
|
+
const secretPath = resolveSecretPath(name);
|
|
8849
|
+
return crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new SecretStoreTemporaryPathError({
|
|
8850
|
+
resource: `secret ${name}`,
|
|
8851
|
+
cause
|
|
8852
|
+
})), Effect.flatMap((uuid) => {
|
|
8853
|
+
const tempPath = `${secretPath}.${uuid}.tmp`;
|
|
8854
|
+
return Effect.gen(function* () {
|
|
8855
|
+
yield* fileSystem.writeFile(tempPath, value);
|
|
8856
|
+
yield* fileSystem.chmod(tempPath, 384);
|
|
8857
|
+
yield* fileSystem.rename(tempPath, secretPath);
|
|
8858
|
+
yield* fileSystem.chmod(secretPath, 384);
|
|
8859
|
+
}).pipe(Effect.catch((cause) => fileSystem.remove(tempPath).pipe(Effect.ignore, Effect.flatMap(() => Effect.fail(new SecretStorePersistError({
|
|
8860
|
+
resource: `secret ${name}`,
|
|
8861
|
+
cause
|
|
8862
|
+
}))))));
|
|
8863
|
+
}), Effect.withSpan("ServerSecretStore.set"));
|
|
8864
|
+
};
|
|
8865
|
+
const create = (name, value) => {
|
|
8866
|
+
const secretPath = resolveSecretPath(name);
|
|
8867
|
+
return Effect.scoped(Effect.gen(function* () {
|
|
8868
|
+
const file = yield* fileSystem.open(secretPath, {
|
|
8869
|
+
flag: "wx",
|
|
8870
|
+
mode: 384
|
|
8871
|
+
});
|
|
8872
|
+
yield* file.writeAll(value);
|
|
8873
|
+
yield* file.sync;
|
|
8874
|
+
yield* fileSystem.chmod(secretPath, 384);
|
|
8875
|
+
})).pipe(Effect.mapError((cause) => new SecretStorePersistError({
|
|
8876
|
+
resource: `secret ${name}`,
|
|
8877
|
+
cause
|
|
8878
|
+
})));
|
|
8879
|
+
};
|
|
8880
|
+
const getOrCreateRandom = (name, bytes) => get(name).pipe(Effect.flatMap(Option.match({
|
|
8881
|
+
onSome: Effect.succeed,
|
|
8882
|
+
onNone: () => crypto.randomBytes(bytes).pipe(Effect.mapError((cause) => new SecretStoreRandomGenerationError({
|
|
8883
|
+
resource: `secret ${name}`,
|
|
8884
|
+
cause
|
|
8885
|
+
})), Effect.flatMap((generated) => create(name, generated).pipe(Effect.as(Uint8Array.from(generated)), Effect.catchIf(isSecretStoreError, (error) => isSecretAlreadyExistsError(error) ? get(name).pipe(Effect.flatMap(Option.match({
|
|
8886
|
+
onSome: Effect.succeed,
|
|
8887
|
+
onNone: () => Effect.fail(new SecretStoreConcurrentReadError({ resource: `secret ${name}` }))
|
|
8888
|
+
}))) : Effect.fail(error)))))
|
|
8889
|
+
})), Effect.withSpan("ServerSecretStore.getOrCreateRandom"));
|
|
8890
|
+
const remove = (name) => fileSystem.remove(resolveSecretPath(name)).pipe(Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.void : Effect.fail(new SecretStoreRemoveError({
|
|
8891
|
+
resource: `secret ${name}`,
|
|
8892
|
+
cause
|
|
8893
|
+
}))), Effect.withSpan("ServerSecretStore.remove"));
|
|
8894
|
+
return ServerSecretStore.of({
|
|
8895
|
+
get,
|
|
8896
|
+
set,
|
|
8897
|
+
create,
|
|
8898
|
+
getOrCreateRandom,
|
|
8899
|
+
remove
|
|
8900
|
+
});
|
|
8901
|
+
});
|
|
8902
|
+
const layer$62 = Layer.effect(ServerSecretStore, make$66);
|
|
8903
|
+
//#endregion
|
|
8904
|
+
//#region src/auth/SessionStore.ts
|
|
8905
|
+
var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
|
|
8709
8906
|
get message() {
|
|
8710
|
-
return "
|
|
8907
|
+
return "Malformed session token.";
|
|
8711
8908
|
}
|
|
8712
8909
|
};
|
|
8713
|
-
var
|
|
8714
|
-
pairingLinkId: Schema$1.String,
|
|
8715
|
-
cause: Schema$1.Defect()
|
|
8716
|
-
}) {
|
|
8910
|
+
var InvalidSessionTokenSignatureError = class extends Schema$1.TaggedErrorClass()("InvalidSessionTokenSignatureError", {}) {
|
|
8717
8911
|
get message() {
|
|
8718
|
-
return
|
|
8912
|
+
return "Invalid session token signature.";
|
|
8719
8913
|
}
|
|
8720
8914
|
};
|
|
8721
|
-
var
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8915
|
+
var InvalidSessionTokenPayloadError = class extends Schema$1.TaggedErrorClass()("InvalidSessionTokenPayloadError", { cause: Schema$1.Defect() }) {
|
|
8916
|
+
get message() {
|
|
8917
|
+
return "Invalid session token payload.";
|
|
8918
|
+
}
|
|
8919
|
+
};
|
|
8920
|
+
var SessionTokenExpiredError = class extends Schema$1.TaggedErrorClass()("SessionTokenExpiredError", {
|
|
8921
|
+
sessionId: AuthSessionId,
|
|
8922
|
+
expiresAt: Schema$1.DateTimeUtc,
|
|
8923
|
+
observedAt: Schema$1.DateTimeUtc
|
|
8726
8924
|
}) {
|
|
8727
8925
|
get message() {
|
|
8728
|
-
return
|
|
8926
|
+
return "Session token expired.";
|
|
8729
8927
|
}
|
|
8730
8928
|
};
|
|
8731
|
-
var
|
|
8732
|
-
|
|
8733
|
-
|
|
8929
|
+
var UnknownSessionTokenError = class extends Schema$1.TaggedErrorClass()("UnknownSessionTokenError", { sessionId: AuthSessionId }) {
|
|
8930
|
+
get message() {
|
|
8931
|
+
return "Unknown session token.";
|
|
8932
|
+
}
|
|
8933
|
+
};
|
|
8934
|
+
var SessionTokenRevokedError = class extends Schema$1.TaggedErrorClass()("SessionTokenRevokedError", {
|
|
8935
|
+
sessionId: AuthSessionId,
|
|
8936
|
+
revokedAt: Schema$1.DateTimeUtc
|
|
8734
8937
|
}) {
|
|
8735
8938
|
get message() {
|
|
8736
|
-
return
|
|
8939
|
+
return "Session token revoked.";
|
|
8737
8940
|
}
|
|
8738
8941
|
};
|
|
8739
|
-
var
|
|
8942
|
+
var InvalidSessionExpirationClaimError = class extends Schema$1.TaggedErrorClass()("InvalidSessionExpirationClaimError", {
|
|
8943
|
+
sessionId: AuthSessionId,
|
|
8944
|
+
expirationClaim: Schema$1.Number
|
|
8945
|
+
}) {
|
|
8740
8946
|
get message() {
|
|
8741
|
-
return "
|
|
8947
|
+
return "Invalid `exp` claim";
|
|
8742
8948
|
}
|
|
8743
8949
|
};
|
|
8744
|
-
var
|
|
8950
|
+
var MalformedWebSocketTokenError = class extends Schema$1.TaggedErrorClass()("MalformedWebSocketTokenError", {}) {
|
|
8745
8951
|
get message() {
|
|
8746
|
-
return "
|
|
8952
|
+
return "Malformed websocket token.";
|
|
8747
8953
|
}
|
|
8748
8954
|
};
|
|
8749
|
-
var
|
|
8955
|
+
var InvalidWebSocketTokenSignatureError = class extends Schema$1.TaggedErrorClass()("InvalidWebSocketTokenSignatureError", {}) {
|
|
8750
8956
|
get message() {
|
|
8751
|
-
return "
|
|
8957
|
+
return "Invalid websocket token signature.";
|
|
8752
8958
|
}
|
|
8753
8959
|
};
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
PairingCredentialIssueError,
|
|
8758
|
-
PairingCredentialRandomGenerationError,
|
|
8759
|
-
BootstrapCredentialConsumeError,
|
|
8760
|
-
BootstrapCredentialConsumeAvailableError,
|
|
8761
|
-
BootstrapCredentialLookupError
|
|
8762
|
-
]);
|
|
8763
|
-
const isBootstrapCredentialInternalError = Schema$1.is(BootstrapCredentialInternalError);
|
|
8764
|
-
const BootstrapCredentialError = Schema$1.Union([BootstrapCredentialInvalidError, BootstrapCredentialInternalError]);
|
|
8765
|
-
Schema$1.is(BootstrapCredentialError);
|
|
8766
|
-
var PairingGrantStore = class extends Context.Service()("@p4code/cli/auth/PairingGrantStore") {};
|
|
8767
|
-
const DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES = Duration.minutes(5);
|
|
8768
|
-
const DESKTOP_BOOTSTRAP_TTL_HOURS = Duration.hours(24);
|
|
8769
|
-
const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
|
|
8770
|
-
const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
8771
|
-
const PAIRING_TOKEN_LENGTH = 12;
|
|
8772
|
-
const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
|
|
8773
|
-
const make$66 = Effect.gen(function* () {
|
|
8774
|
-
const crypto = yield* Crypto.Crypto;
|
|
8775
|
-
const config = yield* ServerConfig$1;
|
|
8776
|
-
const pairingLinks = yield* AuthPairingLinkRepository;
|
|
8777
|
-
const seededGrantsRef = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
8778
|
-
const changesPubSub = yield* PubSub.unbounded();
|
|
8779
|
-
const generatePairingToken = Effect.gen(function* () {
|
|
8780
|
-
let credential = "";
|
|
8781
|
-
while (credential.length < PAIRING_TOKEN_LENGTH) {
|
|
8782
|
-
const bytes = yield* crypto.randomBytes(PAIRING_TOKEN_LENGTH).pipe(Effect.mapError((cause) => new PairingCredentialRandomGenerationError({
|
|
8783
|
-
operation: "generate-token",
|
|
8784
|
-
cause
|
|
8785
|
-
})));
|
|
8786
|
-
for (const byte of bytes) {
|
|
8787
|
-
if (byte >= PAIRING_TOKEN_REJECTION_LIMIT) continue;
|
|
8788
|
-
credential += PAIRING_TOKEN_ALPHABET[byte % 32];
|
|
8789
|
-
if (credential.length === PAIRING_TOKEN_LENGTH) return credential;
|
|
8790
|
-
}
|
|
8791
|
-
}
|
|
8792
|
-
return credential;
|
|
8793
|
-
});
|
|
8794
|
-
const seedGrant = (credential, grant) => Ref.update(seededGrantsRef, (current) => {
|
|
8795
|
-
const next = new Map(current);
|
|
8796
|
-
next.set(credential, grant);
|
|
8797
|
-
return next;
|
|
8798
|
-
});
|
|
8799
|
-
const emitUpsert = (pairingLink) => PubSub.publish(changesPubSub, {
|
|
8800
|
-
type: "pairingLinkUpserted",
|
|
8801
|
-
pairingLink
|
|
8802
|
-
}).pipe(Effect.asVoid);
|
|
8803
|
-
const emitRemoved = (id) => PubSub.publish(changesPubSub, {
|
|
8804
|
-
type: "pairingLinkRemoved",
|
|
8805
|
-
id
|
|
8806
|
-
}).pipe(Effect.asVoid);
|
|
8807
|
-
if (config.desktopBootstrapToken) {
|
|
8808
|
-
const now = yield* DateTime.now;
|
|
8809
|
-
yield* seedGrant(config.desktopBootstrapToken, {
|
|
8810
|
-
method: "desktop-bootstrap",
|
|
8811
|
-
scopes: AuthAdministrativeScopes,
|
|
8812
|
-
subject: "desktop-bootstrap",
|
|
8813
|
-
expiresAt: DateTime.add(now, { milliseconds: Duration.toMillis(DESKTOP_BOOTSTRAP_TTL_HOURS) }),
|
|
8814
|
-
remainingUses: "unbounded"
|
|
8815
|
-
});
|
|
8960
|
+
var InvalidWebSocketTokenPayloadError = class extends Schema$1.TaggedErrorClass()("InvalidWebSocketTokenPayloadError", { cause: Schema$1.Defect() }) {
|
|
8961
|
+
get message() {
|
|
8962
|
+
return "Invalid websocket token payload.";
|
|
8816
8963
|
}
|
|
8817
|
-
const listActive = Effect.fn("PairingGrantStore.listActive")(function* () {
|
|
8818
|
-
const now = yield* DateTime.now;
|
|
8819
|
-
return (yield* pairingLinks.listActive({ now })).map((row) => row.label ? {
|
|
8820
|
-
id: row.id,
|
|
8821
|
-
credential: row.credential,
|
|
8822
|
-
scopes: row.scopes,
|
|
8823
|
-
subject: row.subject,
|
|
8824
|
-
label: row.label,
|
|
8825
|
-
createdAt: row.createdAt,
|
|
8826
|
-
expiresAt: row.expiresAt
|
|
8827
|
-
} : {
|
|
8828
|
-
id: row.id,
|
|
8829
|
-
credential: row.credential,
|
|
8830
|
-
scopes: row.scopes,
|
|
8831
|
-
subject: row.subject,
|
|
8832
|
-
createdAt: row.createdAt,
|
|
8833
|
-
expiresAt: row.expiresAt
|
|
8834
|
-
});
|
|
8835
|
-
}, Effect.mapError((cause) => new ActivePairingLinksLoadError({ cause })));
|
|
8836
|
-
const revoke = Effect.fn("PairingGrantStore.revoke")(function* (id) {
|
|
8837
|
-
const revokedAt = yield* DateTime.now;
|
|
8838
|
-
const revoked = yield* pairingLinks.revoke({
|
|
8839
|
-
id,
|
|
8840
|
-
revokedAt
|
|
8841
|
-
}).pipe(Effect.mapError((cause) => new PairingLinkRevokeError({
|
|
8842
|
-
pairingLinkId: id,
|
|
8843
|
-
cause
|
|
8844
|
-
})));
|
|
8845
|
-
if (revoked) yield* emitRemoved(id);
|
|
8846
|
-
return revoked;
|
|
8847
|
-
});
|
|
8848
|
-
const issueOneTimeToken = Effect.fn("PairingGrantStore.issueOneTimeToken")(function* (input) {
|
|
8849
|
-
const id = yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new PairingCredentialRandomGenerationError({
|
|
8850
|
-
operation: "generate-id",
|
|
8851
|
-
cause
|
|
8852
|
-
})));
|
|
8853
|
-
const credential = yield* generatePairingToken;
|
|
8854
|
-
const isDevStartupToken = config.devUrl !== void 0 && input?.purpose === "startup";
|
|
8855
|
-
const ttl = input?.ttl ?? (isDevStartupToken ? DEV_STARTUP_TTL_HOURS : DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES);
|
|
8856
|
-
const now = yield* DateTime.now;
|
|
8857
|
-
const expiresAt = DateTime.add(now, { milliseconds: Duration.toMillis(ttl) });
|
|
8858
|
-
const issued = {
|
|
8859
|
-
id,
|
|
8860
|
-
credential,
|
|
8861
|
-
...input?.label ? { label: input.label } : {},
|
|
8862
|
-
...input?.proofKeyThumbprint ? { proofKeyThumbprint: input.proofKeyThumbprint } : {},
|
|
8863
|
-
expiresAt
|
|
8864
|
-
};
|
|
8865
|
-
const subject = input?.subject ?? "one-time-token";
|
|
8866
|
-
yield* pairingLinks.create({
|
|
8867
|
-
id,
|
|
8868
|
-
credential,
|
|
8869
|
-
method: "one-time-token",
|
|
8870
|
-
scopes: input?.scopes ?? AuthStandardClientScopes,
|
|
8871
|
-
subject,
|
|
8872
|
-
label: input?.label ?? null,
|
|
8873
|
-
proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
|
|
8874
|
-
createdAt: now,
|
|
8875
|
-
expiresAt
|
|
8876
|
-
}).pipe(Effect.mapError((cause) => new PairingCredentialIssueError({
|
|
8877
|
-
pairingLinkId: id,
|
|
8878
|
-
subject,
|
|
8879
|
-
...input?.label ? { label: input.label } : {},
|
|
8880
|
-
cause
|
|
8881
|
-
})));
|
|
8882
|
-
yield* emitUpsert({
|
|
8883
|
-
id,
|
|
8884
|
-
credential,
|
|
8885
|
-
scopes: input?.scopes ?? AuthStandardClientScopes,
|
|
8886
|
-
subject: input?.subject ?? "one-time-token",
|
|
8887
|
-
...input?.label ? { label: input.label } : {},
|
|
8888
|
-
createdAt: now,
|
|
8889
|
-
expiresAt
|
|
8890
|
-
});
|
|
8891
|
-
return issued;
|
|
8892
|
-
});
|
|
8893
|
-
const consume = Effect.fn("PairingGrantStore.consume")(function* (credential, input) {
|
|
8894
|
-
const now = yield* DateTime.now;
|
|
8895
|
-
const seededResult = yield* Ref.modify(seededGrantsRef, (current) => {
|
|
8896
|
-
const grant = current.get(credential);
|
|
8897
|
-
if (!grant) return [{
|
|
8898
|
-
_tag: "error",
|
|
8899
|
-
reason: "not-found",
|
|
8900
|
-
error: new UnknownBootstrapCredentialError({})
|
|
8901
|
-
}, current];
|
|
8902
|
-
const next = new Map(current);
|
|
8903
|
-
if (DateTime.isGreaterThanOrEqualTo(now, grant.expiresAt)) {
|
|
8904
|
-
next.delete(credential);
|
|
8905
|
-
return [{
|
|
8906
|
-
_tag: "error",
|
|
8907
|
-
reason: "expired",
|
|
8908
|
-
error: new ExpiredBootstrapCredentialError({})
|
|
8909
|
-
}, next];
|
|
8910
|
-
}
|
|
8911
|
-
if (grant.proofKeyThumbprint && grant.proofKeyThumbprint !== input?.proofKeyThumbprint) return [{
|
|
8912
|
-
_tag: "error",
|
|
8913
|
-
reason: "not-found",
|
|
8914
|
-
error: new BootstrapCredentialProofKeyMismatchError({})
|
|
8915
|
-
}, next];
|
|
8916
|
-
const remainingUses = grant.remainingUses;
|
|
8917
|
-
if (typeof remainingUses === "number") if (remainingUses <= 1) next.delete(credential);
|
|
8918
|
-
else next.set(credential, {
|
|
8919
|
-
...grant,
|
|
8920
|
-
remainingUses: remainingUses - 1
|
|
8921
|
-
});
|
|
8922
|
-
return [{
|
|
8923
|
-
_tag: "success",
|
|
8924
|
-
grant: {
|
|
8925
|
-
method: grant.method,
|
|
8926
|
-
scopes: grant.scopes,
|
|
8927
|
-
subject: grant.subject,
|
|
8928
|
-
...grant.label ? { label: grant.label } : {},
|
|
8929
|
-
...grant.proofKeyThumbprint ? { proofKeyThumbprint: grant.proofKeyThumbprint } : {},
|
|
8930
|
-
expiresAt: grant.expiresAt
|
|
8931
|
-
}
|
|
8932
|
-
}, next];
|
|
8933
|
-
});
|
|
8934
|
-
if (seededResult._tag === "success") return seededResult.grant;
|
|
8935
|
-
if (seededResult.reason !== "not-found") return yield* seededResult.error;
|
|
8936
|
-
const consumed = yield* pairingLinks.consumeAvailable({
|
|
8937
|
-
credential,
|
|
8938
|
-
proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
|
|
8939
|
-
consumedAt: now,
|
|
8940
|
-
now
|
|
8941
|
-
}).pipe(Effect.mapError((cause) => new BootstrapCredentialConsumeAvailableError({ cause })));
|
|
8942
|
-
if (Option.isSome(consumed)) {
|
|
8943
|
-
yield* emitRemoved(consumed.value.id);
|
|
8944
|
-
return {
|
|
8945
|
-
method: consumed.value.method,
|
|
8946
|
-
scopes: consumed.value.scopes,
|
|
8947
|
-
subject: consumed.value.subject,
|
|
8948
|
-
...consumed.value.label ? { label: consumed.value.label } : {},
|
|
8949
|
-
...consumed.value.proofKeyThumbprint ? { proofKeyThumbprint: consumed.value.proofKeyThumbprint } : {},
|
|
8950
|
-
expiresAt: consumed.value.expiresAt
|
|
8951
|
-
};
|
|
8952
|
-
}
|
|
8953
|
-
const matching = yield* pairingLinks.getByCredential({ credential }).pipe(Effect.mapError((cause) => new BootstrapCredentialLookupError({ cause })));
|
|
8954
|
-
if (Option.isNone(matching)) return yield* new UnknownBootstrapCredentialError({});
|
|
8955
|
-
if (matching.value.revokedAt !== null) return yield* new UnavailableBootstrapCredentialError({});
|
|
8956
|
-
if (matching.value.consumedAt !== null) return yield* new UnknownBootstrapCredentialError({});
|
|
8957
|
-
if (DateTime.isGreaterThanOrEqualTo(now, matching.value.expiresAt)) return yield* new ExpiredBootstrapCredentialError({});
|
|
8958
|
-
if (matching.value.proofKeyThumbprint !== null && matching.value.proofKeyThumbprint !== input?.proofKeyThumbprint) return yield* new BootstrapCredentialProofKeyMismatchError({});
|
|
8959
|
-
return yield* new UnavailableBootstrapCredentialError({});
|
|
8960
|
-
});
|
|
8961
|
-
return PairingGrantStore.of({
|
|
8962
|
-
issueOneTimeToken,
|
|
8963
|
-
listActive,
|
|
8964
|
-
get streamChanges() {
|
|
8965
|
-
return Stream.fromPubSub(changesPubSub);
|
|
8966
|
-
},
|
|
8967
|
-
revoke,
|
|
8968
|
-
consume
|
|
8969
|
-
});
|
|
8970
|
-
});
|
|
8971
|
-
const layer$62 = Layer.effect(PairingGrantStore, make$66).pipe(Layer.provideMerge(layer$63));
|
|
8972
|
-
//#endregion
|
|
8973
|
-
//#region src/auth/ServerSecretStore.ts
|
|
8974
|
-
const secretStoreErrorContext = {
|
|
8975
|
-
resource: Schema$1.String,
|
|
8976
|
-
cause: Schema$1.Defect()
|
|
8977
8964
|
};
|
|
8978
|
-
var
|
|
8965
|
+
var WebSocketTokenExpiredError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenExpiredError", {
|
|
8966
|
+
sessionId: AuthSessionId,
|
|
8967
|
+
expiresAt: Schema$1.DateTimeUtc,
|
|
8968
|
+
observedAt: Schema$1.DateTimeUtc
|
|
8969
|
+
}) {
|
|
8979
8970
|
get message() {
|
|
8980
|
-
return
|
|
8971
|
+
return "Websocket token expired.";
|
|
8981
8972
|
}
|
|
8982
8973
|
};
|
|
8983
|
-
var
|
|
8974
|
+
var UnknownWebSocketSessionError = class extends Schema$1.TaggedErrorClass()("UnknownWebSocketSessionError", { sessionId: AuthSessionId }) {
|
|
8984
8975
|
get message() {
|
|
8985
|
-
return
|
|
8976
|
+
return "Unknown websocket session.";
|
|
8986
8977
|
}
|
|
8987
8978
|
};
|
|
8988
|
-
var
|
|
8979
|
+
var WebSocketSessionExpiredError = class extends Schema$1.TaggedErrorClass()("WebSocketSessionExpiredError", {
|
|
8980
|
+
sessionId: AuthSessionId,
|
|
8981
|
+
expiresAt: Schema$1.DateTimeUtc,
|
|
8982
|
+
observedAt: Schema$1.DateTimeUtc
|
|
8983
|
+
}) {
|
|
8989
8984
|
get message() {
|
|
8990
|
-
return
|
|
8985
|
+
return "Websocket session expired.";
|
|
8991
8986
|
}
|
|
8992
8987
|
};
|
|
8993
|
-
var
|
|
8988
|
+
var WebSocketSessionRevokedError = class extends Schema$1.TaggedErrorClass()("WebSocketSessionRevokedError", {
|
|
8989
|
+
sessionId: AuthSessionId,
|
|
8990
|
+
revokedAt: Schema$1.DateTimeUtc
|
|
8991
|
+
}) {
|
|
8994
8992
|
get message() {
|
|
8995
|
-
return
|
|
8993
|
+
return "Websocket session revoked.";
|
|
8996
8994
|
}
|
|
8997
8995
|
};
|
|
8998
|
-
|
|
8996
|
+
const SessionCredentialInvalidError = Schema$1.Union([
|
|
8997
|
+
MalformedSessionTokenError,
|
|
8998
|
+
InvalidSessionTokenSignatureError,
|
|
8999
|
+
InvalidSessionTokenPayloadError,
|
|
9000
|
+
SessionTokenExpiredError,
|
|
9001
|
+
UnknownSessionTokenError,
|
|
9002
|
+
SessionTokenRevokedError,
|
|
9003
|
+
InvalidSessionExpirationClaimError,
|
|
9004
|
+
MalformedWebSocketTokenError,
|
|
9005
|
+
InvalidWebSocketTokenSignatureError,
|
|
9006
|
+
InvalidWebSocketTokenPayloadError,
|
|
9007
|
+
WebSocketTokenExpiredError,
|
|
9008
|
+
UnknownWebSocketSessionError,
|
|
9009
|
+
WebSocketSessionExpiredError,
|
|
9010
|
+
WebSocketSessionRevokedError
|
|
9011
|
+
]);
|
|
9012
|
+
const isSessionCredentialInvalidError = Schema$1.is(SessionCredentialInvalidError);
|
|
9013
|
+
const sessionCredentialInternalErrorContext = { cause: Schema$1.Defect() };
|
|
9014
|
+
var SessionClaimsEncodingError = class extends Schema$1.TaggedErrorClass()("SessionClaimsEncodingError", {
|
|
9015
|
+
sessionId: AuthSessionId,
|
|
9016
|
+
operation: Schema$1.Literals(["encode_session_claims", "encode_websocket_claims"]),
|
|
9017
|
+
...sessionCredentialInternalErrorContext
|
|
9018
|
+
}) {
|
|
8999
9019
|
get message() {
|
|
9000
|
-
return
|
|
9020
|
+
return "Failed to encode claims";
|
|
9001
9021
|
}
|
|
9002
9022
|
};
|
|
9003
|
-
var
|
|
9023
|
+
var SessionCredentialIssueError = class extends Schema$1.TaggedErrorClass()("SessionCredentialIssueError", {
|
|
9024
|
+
sessionId: Schema$1.optional(AuthSessionId),
|
|
9025
|
+
...sessionCredentialInternalErrorContext
|
|
9026
|
+
}) {
|
|
9004
9027
|
get message() {
|
|
9005
|
-
return
|
|
9028
|
+
return "Failed to issue session credential.";
|
|
9006
9029
|
}
|
|
9007
9030
|
};
|
|
9008
|
-
var
|
|
9031
|
+
var SessionCredentialVerificationError = class extends Schema$1.TaggedErrorClass()("SessionCredentialVerificationError", {
|
|
9032
|
+
sessionId: AuthSessionId,
|
|
9033
|
+
...sessionCredentialInternalErrorContext
|
|
9034
|
+
}) {
|
|
9009
9035
|
get message() {
|
|
9010
|
-
return
|
|
9036
|
+
return "Failed to verify session credential.";
|
|
9011
9037
|
}
|
|
9012
9038
|
};
|
|
9013
|
-
var
|
|
9039
|
+
var WebSocketTokenIssueError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenIssueError", {
|
|
9040
|
+
sessionId: AuthSessionId,
|
|
9041
|
+
...sessionCredentialInternalErrorContext
|
|
9042
|
+
}) {
|
|
9014
9043
|
get message() {
|
|
9015
|
-
return
|
|
9044
|
+
return "Failed to issue websocket token.";
|
|
9016
9045
|
}
|
|
9017
9046
|
};
|
|
9018
|
-
var
|
|
9047
|
+
var WebSocketTokenVerificationError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenVerificationError", {
|
|
9048
|
+
sessionId: AuthSessionId,
|
|
9049
|
+
...sessionCredentialInternalErrorContext
|
|
9050
|
+
}) {
|
|
9019
9051
|
get message() {
|
|
9020
|
-
return
|
|
9052
|
+
return "Failed to verify websocket token.";
|
|
9021
9053
|
}
|
|
9022
9054
|
};
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9055
|
+
var ActiveSessionsListError = class extends Schema$1.TaggedErrorClass()("ActiveSessionsListError", { ...sessionCredentialInternalErrorContext }) {
|
|
9056
|
+
get message() {
|
|
9057
|
+
return "Failed to list active sessions.";
|
|
9058
|
+
}
|
|
9059
|
+
};
|
|
9060
|
+
var SessionRevocationError = class extends Schema$1.TaggedErrorClass()("SessionRevocationError", {
|
|
9061
|
+
sessionId: AuthSessionId,
|
|
9062
|
+
...sessionCredentialInternalErrorContext
|
|
9063
|
+
}) {
|
|
9064
|
+
get message() {
|
|
9065
|
+
return "Failed to revoke session.";
|
|
9066
|
+
}
|
|
9067
|
+
};
|
|
9068
|
+
var OtherSessionsRevocationError = class extends Schema$1.TaggedErrorClass()("OtherSessionsRevocationError", {
|
|
9069
|
+
currentSessionId: AuthSessionId,
|
|
9070
|
+
...sessionCredentialInternalErrorContext
|
|
9071
|
+
}) {
|
|
9072
|
+
get message() {
|
|
9073
|
+
return "Failed to revoke other sessions.";
|
|
9074
|
+
}
|
|
9075
|
+
};
|
|
9076
|
+
const SessionCredentialInternalError = Schema$1.Union([
|
|
9077
|
+
SessionClaimsEncodingError,
|
|
9078
|
+
SessionCredentialIssueError,
|
|
9079
|
+
SessionCredentialVerificationError,
|
|
9080
|
+
WebSocketTokenIssueError,
|
|
9081
|
+
WebSocketTokenVerificationError,
|
|
9082
|
+
ActiveSessionsListError,
|
|
9083
|
+
SessionRevocationError,
|
|
9084
|
+
OtherSessionsRevocationError
|
|
9033
9085
|
]);
|
|
9034
|
-
|
|
9035
|
-
const
|
|
9036
|
-
|
|
9037
|
-
|
|
9086
|
+
Schema$1.is(SessionCredentialInternalError);
|
|
9087
|
+
const SessionCredentialError = Schema$1.Union([SessionCredentialInvalidError, SessionCredentialInternalError]);
|
|
9088
|
+
Schema$1.is(SessionCredentialError);
|
|
9089
|
+
/**
|
|
9090
|
+
* Subject the desktop bootstrap grant issues sessions under.
|
|
9091
|
+
*
|
|
9092
|
+
* Exported so the grant seed and the supersede rule cannot drift: they are the
|
|
9093
|
+
* two halves of one decision, and a typo in either would silently turn the
|
|
9094
|
+
* cleanup off.
|
|
9095
|
+
*/
|
|
9096
|
+
const DESKTOP_BOOTSTRAP_SUBJECT = "desktop-bootstrap";
|
|
9097
|
+
var SessionStore = class extends Context.Service()("@p4code/cli/auth/SessionStore") {};
|
|
9098
|
+
const SIGNING_SECRET_NAME$1 = "server-signing-key";
|
|
9099
|
+
const DEFAULT_SESSION_TTL = Duration.days(30);
|
|
9100
|
+
const DEFAULT_WEBSOCKET_TOKEN_TTL = Duration.minutes(5);
|
|
9101
|
+
const SessionClaims = Schema$1.Struct({
|
|
9102
|
+
v: Schema$1.Literal(1),
|
|
9103
|
+
kind: Schema$1.Literal("session"),
|
|
9104
|
+
sid: AuthSessionId,
|
|
9105
|
+
sub: Schema$1.String,
|
|
9106
|
+
scopes: AuthEnvironmentScopes,
|
|
9107
|
+
method: Schema$1.Literals(["browser-session-cookie", "bearer-access-token"]),
|
|
9108
|
+
jkt: Schema$1.optionalKey(Schema$1.String),
|
|
9109
|
+
iat: Schema$1.Number,
|
|
9110
|
+
exp: Schema$1.Number
|
|
9111
|
+
});
|
|
9112
|
+
const WebSocketClaims = Schema$1.Struct({
|
|
9113
|
+
v: Schema$1.Literal(1),
|
|
9114
|
+
kind: Schema$1.Literal("websocket"),
|
|
9115
|
+
sid: AuthSessionId,
|
|
9116
|
+
iat: Schema$1.Number,
|
|
9117
|
+
exp: Schema$1.Number
|
|
9118
|
+
});
|
|
9119
|
+
const decodeSessionClaims = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(SessionClaims));
|
|
9120
|
+
const decodeWebSocketClaims = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(WebSocketClaims));
|
|
9121
|
+
function createDefaultClientMetadata() {
|
|
9122
|
+
return { deviceType: "unknown" };
|
|
9123
|
+
}
|
|
9124
|
+
function toClientMetadata(record) {
|
|
9125
|
+
return {
|
|
9126
|
+
...record.label ? { label: record.label } : {},
|
|
9127
|
+
...record.ipAddress ? { ipAddress: record.ipAddress } : {},
|
|
9128
|
+
...record.userAgent ? { userAgent: record.userAgent } : {},
|
|
9129
|
+
deviceType: record.deviceType,
|
|
9130
|
+
...record.os ? { os: record.os } : {},
|
|
9131
|
+
...record.browser ? { browser: record.browser } : {}
|
|
9132
|
+
};
|
|
9133
|
+
}
|
|
9134
|
+
function toAuthClientSession(input) {
|
|
9135
|
+
return {
|
|
9136
|
+
...input,
|
|
9137
|
+
current: false
|
|
9138
|
+
};
|
|
9139
|
+
}
|
|
9038
9140
|
const make$65 = Effect.gen(function* () {
|
|
9039
9141
|
const crypto = yield* Crypto.Crypto;
|
|
9040
|
-
const fileSystem = yield* FileSystem.FileSystem;
|
|
9041
|
-
const path = yield* Path.Path;
|
|
9042
9142
|
const serverConfig = yield* ServerConfig$1;
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9143
|
+
const secretStore = yield* ServerSecretStore;
|
|
9144
|
+
const authSessions = yield* AuthSessionRepository;
|
|
9145
|
+
const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME$1, 32);
|
|
9146
|
+
const connectedSessionsRef = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
9147
|
+
const changesPubSub = yield* PubSub.unbounded();
|
|
9148
|
+
const cookieName = resolveSessionCookieName({
|
|
9149
|
+
mode: serverConfig.mode,
|
|
9150
|
+
port: serverConfig.port,
|
|
9151
|
+
host: serverConfig.host,
|
|
9152
|
+
instanceKey: serverConfig.stateDir,
|
|
9153
|
+
development: serverConfig.devUrl !== void 0
|
|
9154
|
+
});
|
|
9155
|
+
const emitUpsert = (clientSession) => PubSub.publish(changesPubSub, {
|
|
9156
|
+
type: "clientUpserted",
|
|
9157
|
+
clientSession
|
|
9158
|
+
}).pipe(Effect.asVoid);
|
|
9159
|
+
const emitRemoved = (sessionId) => PubSub.publish(changesPubSub, {
|
|
9160
|
+
type: "clientRemoved",
|
|
9161
|
+
sessionId
|
|
9162
|
+
}).pipe(Effect.asVoid);
|
|
9163
|
+
const loadActiveSession = (sessionId) => Effect.gen(function* () {
|
|
9164
|
+
const row = yield* authSessions.getById({ sessionId });
|
|
9165
|
+
if (Option.isNone(row) || row.value.revokedAt !== null) return Option.none();
|
|
9166
|
+
const connectedSessions = yield* Ref.get(connectedSessionsRef);
|
|
9167
|
+
return Option.some(toAuthClientSession({
|
|
9168
|
+
sessionId: row.value.sessionId,
|
|
9169
|
+
subject: row.value.subject,
|
|
9170
|
+
scopes: row.value.scopes,
|
|
9171
|
+
method: row.value.method,
|
|
9172
|
+
client: toClientMetadata(row.value.client),
|
|
9173
|
+
issuedAt: row.value.issuedAt,
|
|
9174
|
+
expiresAt: row.value.expiresAt,
|
|
9175
|
+
lastConnectedAt: row.value.lastConnectedAt,
|
|
9176
|
+
connected: connectedSessions.has(row.value.sessionId)
|
|
9177
|
+
}));
|
|
9178
|
+
});
|
|
9179
|
+
const markConnected = (sessionId) => Ref.modify(connectedSessionsRef, (current) => {
|
|
9180
|
+
const next = new Map(current);
|
|
9181
|
+
const wasDisconnected = !next.has(sessionId);
|
|
9182
|
+
next.set(sessionId, (next.get(sessionId) ?? 0) + 1);
|
|
9183
|
+
return [wasDisconnected, next];
|
|
9184
|
+
}).pipe(Effect.flatMap((wasDisconnected) => wasDisconnected ? DateTime.now.pipe(Effect.flatMap((lastConnectedAt) => authSessions.setLastConnectedAt({
|
|
9185
|
+
sessionId,
|
|
9186
|
+
lastConnectedAt
|
|
9187
|
+
}))) : Effect.void), Effect.flatMap(() => loadActiveSession(sessionId)), Effect.flatMap((session) => Option.isSome(session) ? emitUpsert(session.value) : Effect.void), Effect.catchCause((cause) => Effect.logError("Failed to publish connected-session auth update.").pipe(Effect.annotateLogs({
|
|
9188
|
+
sessionId,
|
|
9046
9189
|
cause
|
|
9047
|
-
})));
|
|
9048
|
-
const
|
|
9049
|
-
|
|
9050
|
-
|
|
9190
|
+
}))), Effect.withSpan("SessionStore.markConnected"));
|
|
9191
|
+
const markDisconnected = (sessionId) => Ref.update(connectedSessionsRef, (current) => {
|
|
9192
|
+
const next = new Map(current);
|
|
9193
|
+
const remaining = (next.get(sessionId) ?? 0) - 1;
|
|
9194
|
+
if (remaining > 0) next.set(sessionId, remaining);
|
|
9195
|
+
else next.delete(sessionId);
|
|
9196
|
+
return next;
|
|
9197
|
+
}).pipe(Effect.flatMap(() => loadActiveSession(sessionId)), Effect.flatMap((session) => Option.isSome(session) ? emitUpsert(session.value) : Effect.void), Effect.catchCause((cause) => Effect.logError("Failed to publish disconnected-session auth update.").pipe(Effect.annotateLogs({
|
|
9198
|
+
sessionId,
|
|
9051
9199
|
cause
|
|
9052
|
-
}))), Effect.withSpan("
|
|
9053
|
-
const
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9200
|
+
}))), Effect.withSpan("SessionStore.markDisconnected"));
|
|
9201
|
+
const encodeClaims = Schema$1.encodeEffect(Schema$1.fromJsonString(SessionClaims));
|
|
9202
|
+
/**
|
|
9203
|
+
* Revokes every other live session belonging to the same subject.
|
|
9204
|
+
*
|
|
9205
|
+
* Best effort: a session that could not be retired is a stale row in a list,
|
|
9206
|
+
* which is untidy, and failing the issue over it would mean a desktop app
|
|
9207
|
+
* that cannot start. The next launch tries again.
|
|
9208
|
+
*/
|
|
9209
|
+
const supersedePriorSessions = Effect.fn("SessionStore.supersedePriorSessions")(function* (keep, subject) {
|
|
9210
|
+
const active = yield* listActive().pipe(Effect.orElseSucceed(() => []));
|
|
9211
|
+
for (const session of active) {
|
|
9212
|
+
if (session.sessionId === keep || session.subject !== subject) continue;
|
|
9213
|
+
yield* revoke(session.sessionId).pipe(Effect.ignore);
|
|
9214
|
+
}
|
|
9215
|
+
});
|
|
9216
|
+
const issue = Effect.fn("SessionStore.issue")(function* (input) {
|
|
9217
|
+
const sessionId = AuthSessionId.make(yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new SessionCredentialIssueError({ cause }))));
|
|
9218
|
+
const issuedAt = yield* DateTime.now;
|
|
9219
|
+
const expiresAt = DateTime.add(issuedAt, { milliseconds: Duration.toMillis(input?.ttl ?? DEFAULT_SESSION_TTL) });
|
|
9220
|
+
const claims = {
|
|
9221
|
+
v: 1,
|
|
9222
|
+
kind: "session",
|
|
9223
|
+
sid: sessionId,
|
|
9224
|
+
sub: input?.subject ?? "browser",
|
|
9225
|
+
scopes: input?.scopes ?? AuthStandardClientScopes,
|
|
9226
|
+
method: input?.method ?? "browser-session-cookie",
|
|
9227
|
+
...input?.proofKeyThumbprint ? { jkt: input.proofKeyThumbprint } : {},
|
|
9228
|
+
iat: issuedAt.epochMilliseconds,
|
|
9229
|
+
exp: expiresAt.epochMilliseconds
|
|
9230
|
+
};
|
|
9231
|
+
const encodedPayload = yield* encodeClaims(claims).pipe(Effect.map(base64UrlEncode), Effect.mapError((cause) => new SessionCredentialIssueError({
|
|
9232
|
+
sessionId,
|
|
9233
|
+
cause: new SessionClaimsEncodingError({
|
|
9234
|
+
sessionId,
|
|
9235
|
+
operation: "encode_session_claims",
|
|
9236
|
+
cause
|
|
9237
|
+
})
|
|
9238
|
+
})));
|
|
9239
|
+
const signature = signPayload(encodedPayload, signingSecret);
|
|
9240
|
+
const client = input?.client ?? createDefaultClientMetadata();
|
|
9241
|
+
yield* authSessions.create({
|
|
9242
|
+
sessionId,
|
|
9243
|
+
subject: claims.sub,
|
|
9244
|
+
scopes: claims.scopes,
|
|
9245
|
+
method: claims.method,
|
|
9246
|
+
client: {
|
|
9247
|
+
label: client.label ?? null,
|
|
9248
|
+
ipAddress: client.ipAddress ?? null,
|
|
9249
|
+
userAgent: client.userAgent ?? null,
|
|
9250
|
+
deviceType: client.deviceType,
|
|
9251
|
+
os: client.os ?? null,
|
|
9252
|
+
browser: client.browser ?? null
|
|
9253
|
+
},
|
|
9254
|
+
issuedAt,
|
|
9255
|
+
expiresAt
|
|
9256
|
+
}).pipe(Effect.mapError((cause) => new SessionCredentialIssueError({
|
|
9257
|
+
sessionId,
|
|
9057
9258
|
cause
|
|
9058
|
-
}))
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9259
|
+
})));
|
|
9260
|
+
yield* emitUpsert(toAuthClientSession({
|
|
9261
|
+
sessionId,
|
|
9262
|
+
subject: claims.sub,
|
|
9263
|
+
scopes: claims.scopes,
|
|
9264
|
+
method: claims.method,
|
|
9265
|
+
client,
|
|
9266
|
+
issuedAt,
|
|
9267
|
+
expiresAt,
|
|
9268
|
+
lastConnectedAt: null,
|
|
9269
|
+
connected: false
|
|
9270
|
+
}));
|
|
9271
|
+
if (claims.sub === "desktop-bootstrap") yield* supersedePriorSessions(sessionId, claims.sub);
|
|
9272
|
+
return {
|
|
9273
|
+
sessionId,
|
|
9274
|
+
token: `${encodedPayload}.${signature}`,
|
|
9275
|
+
method: claims.method,
|
|
9276
|
+
client,
|
|
9277
|
+
expiresAt,
|
|
9278
|
+
scopes: claims.scopes,
|
|
9279
|
+
...claims.jkt ? { proofKeyThumbprint: claims.jkt } : {}
|
|
9280
|
+
};
|
|
9281
|
+
});
|
|
9282
|
+
const verify = Effect.fn("SessionStore.verify")(function* (token) {
|
|
9283
|
+
const [encodedPayload, signature] = token.split(".");
|
|
9284
|
+
if (!encodedPayload || !signature) return yield* new MalformedSessionTokenError({});
|
|
9285
|
+
if (!timingSafeEqualBase64Url(signature, signPayload(encodedPayload, signingSecret))) return yield* new InvalidSessionTokenSignatureError({});
|
|
9286
|
+
const claims = yield* decodeSessionClaims(base64UrlDecodeUtf8(encodedPayload)).pipe(Effect.mapError((cause) => new InvalidSessionTokenPayloadError({ cause })));
|
|
9287
|
+
const observedAt = yield* DateTime.now;
|
|
9288
|
+
const expiresAt = DateTime.make(claims.exp);
|
|
9289
|
+
if (Option.isNone(expiresAt)) return yield* new InvalidSessionExpirationClaimError({
|
|
9290
|
+
sessionId: claims.sid,
|
|
9291
|
+
expirationClaim: claims.exp
|
|
9292
|
+
});
|
|
9293
|
+
if (claims.exp <= observedAt.epochMilliseconds) return yield* new SessionTokenExpiredError({
|
|
9294
|
+
sessionId: claims.sid,
|
|
9295
|
+
expiresAt: expiresAt.value,
|
|
9296
|
+
observedAt
|
|
9297
|
+
});
|
|
9298
|
+
const row = yield* authSessions.getById({ sessionId: claims.sid }).pipe(Effect.mapError((cause) => new SessionCredentialVerificationError({
|
|
9299
|
+
sessionId: claims.sid,
|
|
9300
|
+
cause
|
|
9301
|
+
})));
|
|
9302
|
+
if (Option.isNone(row)) return yield* new UnknownSessionTokenError({ sessionId: claims.sid });
|
|
9303
|
+
if (row.value.revokedAt !== null) return yield* new SessionTokenRevokedError({
|
|
9304
|
+
sessionId: claims.sid,
|
|
9305
|
+
revokedAt: row.value.revokedAt
|
|
9306
|
+
});
|
|
9307
|
+
return {
|
|
9308
|
+
sessionId: claims.sid,
|
|
9309
|
+
token,
|
|
9310
|
+
method: claims.method,
|
|
9311
|
+
client: toClientMetadata(row.value.client),
|
|
9312
|
+
expiresAt: expiresAt.value,
|
|
9313
|
+
subject: claims.sub,
|
|
9314
|
+
scopes: claims.scopes,
|
|
9315
|
+
...claims.jkt ? { proofKeyThumbprint: claims.jkt } : {}
|
|
9316
|
+
};
|
|
9317
|
+
});
|
|
9318
|
+
const encodeWsClaims = Schema$1.encodeEffect(Schema$1.fromJsonString(WebSocketClaims));
|
|
9319
|
+
const issueWebSocketToken = Effect.fn("SessionStore.issueWebSocketToken")(function* (sessionId, input) {
|
|
9320
|
+
const issuedAt = yield* DateTime.now;
|
|
9321
|
+
const expiresAt = DateTime.add(issuedAt, { milliseconds: Duration.toMillis(input?.ttl ?? DEFAULT_WEBSOCKET_TOKEN_TTL) });
|
|
9322
|
+
const claims = {
|
|
9323
|
+
v: 1,
|
|
9324
|
+
kind: "websocket",
|
|
9325
|
+
sid: sessionId,
|
|
9326
|
+
iat: issuedAt.epochMilliseconds,
|
|
9327
|
+
exp: expiresAt.epochMilliseconds
|
|
9328
|
+
};
|
|
9329
|
+
const encodedPayload = yield* encodeWsClaims(claims).pipe(Effect.map(base64UrlEncode), Effect.mapError((cause) => new WebSocketTokenIssueError({
|
|
9330
|
+
sessionId,
|
|
9331
|
+
cause: new SessionClaimsEncodingError({
|
|
9332
|
+
sessionId,
|
|
9333
|
+
operation: "encode_websocket_claims",
|
|
9067
9334
|
cause
|
|
9068
|
-
})
|
|
9069
|
-
})
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9335
|
+
})
|
|
9336
|
+
})));
|
|
9337
|
+
return {
|
|
9338
|
+
token: `${encodedPayload}.${signPayload(encodedPayload, signingSecret)}`,
|
|
9339
|
+
expiresAt
|
|
9340
|
+
};
|
|
9341
|
+
});
|
|
9342
|
+
const verifyWebSocketToken = Effect.fn("SessionStore.verifyWebSocketToken")(function* (token) {
|
|
9343
|
+
const [encodedPayload, signature] = token.split(".");
|
|
9344
|
+
if (!encodedPayload || !signature) return yield* new MalformedWebSocketTokenError({});
|
|
9345
|
+
if (!timingSafeEqualBase64Url(signature, signPayload(encodedPayload, signingSecret))) return yield* new InvalidWebSocketTokenSignatureError({});
|
|
9346
|
+
const claims = yield* decodeWebSocketClaims(base64UrlDecodeUtf8(encodedPayload)).pipe(Effect.mapError((cause) => new InvalidWebSocketTokenPayloadError({ cause })));
|
|
9347
|
+
const observedAt = yield* DateTime.now;
|
|
9348
|
+
const expiresAt = DateTime.make(claims.exp);
|
|
9349
|
+
if (Option.isNone(expiresAt)) return yield* new InvalidSessionExpirationClaimError({
|
|
9350
|
+
sessionId: claims.sid,
|
|
9351
|
+
expirationClaim: claims.exp
|
|
9352
|
+
});
|
|
9353
|
+
if (claims.exp <= observedAt.epochMilliseconds) return yield* new WebSocketTokenExpiredError({
|
|
9354
|
+
sessionId: claims.sid,
|
|
9355
|
+
expiresAt: expiresAt.value,
|
|
9356
|
+
observedAt
|
|
9357
|
+
});
|
|
9358
|
+
const row = yield* authSessions.getById({ sessionId: claims.sid }).pipe(Effect.mapError((cause) => new WebSocketTokenVerificationError({
|
|
9359
|
+
sessionId: claims.sid,
|
|
9083
9360
|
cause
|
|
9084
9361
|
})));
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9362
|
+
if (Option.isNone(row)) return yield* new UnknownWebSocketSessionError({ sessionId: claims.sid });
|
|
9363
|
+
if (row.value.expiresAt.epochMilliseconds <= observedAt.epochMilliseconds) return yield* new WebSocketSessionExpiredError({
|
|
9364
|
+
sessionId: claims.sid,
|
|
9365
|
+
expiresAt: row.value.expiresAt,
|
|
9366
|
+
observedAt
|
|
9367
|
+
});
|
|
9368
|
+
if (row.value.revokedAt !== null) return yield* new WebSocketSessionRevokedError({
|
|
9369
|
+
sessionId: claims.sid,
|
|
9370
|
+
revokedAt: row.value.revokedAt
|
|
9371
|
+
});
|
|
9372
|
+
return {
|
|
9373
|
+
sessionId: row.value.sessionId,
|
|
9374
|
+
token,
|
|
9375
|
+
method: row.value.method,
|
|
9376
|
+
client: toClientMetadata(row.value.client),
|
|
9377
|
+
expiresAt: row.value.expiresAt,
|
|
9378
|
+
subject: row.value.subject,
|
|
9379
|
+
scopes: row.value.scopes
|
|
9380
|
+
};
|
|
9381
|
+
});
|
|
9382
|
+
const listActive = Effect.fn("SessionStore.listActive")(function* () {
|
|
9383
|
+
const now = yield* DateTime.now;
|
|
9384
|
+
const connectedSessions = yield* Ref.get(connectedSessionsRef);
|
|
9385
|
+
return (yield* authSessions.listActive({ now })).map((row) => toAuthClientSession({
|
|
9386
|
+
sessionId: row.sessionId,
|
|
9387
|
+
subject: row.subject,
|
|
9388
|
+
scopes: row.scopes,
|
|
9389
|
+
method: row.method,
|
|
9390
|
+
client: toClientMetadata(row.client),
|
|
9391
|
+
issuedAt: row.issuedAt,
|
|
9392
|
+
expiresAt: row.expiresAt,
|
|
9393
|
+
lastConnectedAt: row.lastConnectedAt,
|
|
9394
|
+
connected: connectedSessions.has(row.sessionId)
|
|
9395
|
+
}));
|
|
9396
|
+
}, Effect.mapError((cause) => new ActiveSessionsListError({ cause })));
|
|
9397
|
+
const revoke = Effect.fn("SessionStore.revoke")(function* (sessionId) {
|
|
9398
|
+
const revokedAt = yield* DateTime.now;
|
|
9399
|
+
const revoked = yield* authSessions.revoke({
|
|
9400
|
+
sessionId,
|
|
9401
|
+
revokedAt
|
|
9402
|
+
}).pipe(Effect.mapError((cause) => new SessionRevocationError({
|
|
9403
|
+
sessionId,
|
|
9090
9404
|
cause
|
|
9091
|
-
}))
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9405
|
+
})));
|
|
9406
|
+
if (revoked) {
|
|
9407
|
+
yield* Ref.update(connectedSessionsRef, (current) => {
|
|
9408
|
+
const next = new Map(current);
|
|
9409
|
+
next.delete(sessionId);
|
|
9410
|
+
return next;
|
|
9411
|
+
});
|
|
9412
|
+
yield* emitRemoved(sessionId);
|
|
9413
|
+
}
|
|
9414
|
+
return revoked;
|
|
9415
|
+
});
|
|
9416
|
+
const revokeAllExcept = Effect.fn("SessionStore.revokeAllExcept")(function* (sessionId) {
|
|
9417
|
+
const revokedAt = yield* DateTime.now;
|
|
9418
|
+
const revokedSessionIds = yield* authSessions.revokeAllExcept({
|
|
9419
|
+
currentSessionId: sessionId,
|
|
9420
|
+
revokedAt
|
|
9421
|
+
}).pipe(Effect.mapError((cause) => new OtherSessionsRevocationError({
|
|
9422
|
+
currentSessionId: sessionId,
|
|
9423
|
+
cause
|
|
9424
|
+
})));
|
|
9425
|
+
if (revokedSessionIds.length > 0) {
|
|
9426
|
+
yield* Ref.update(connectedSessionsRef, (current) => {
|
|
9427
|
+
const next = new Map(current);
|
|
9428
|
+
for (const revokedSessionId of revokedSessionIds) next.delete(revokedSessionId);
|
|
9429
|
+
return next;
|
|
9430
|
+
});
|
|
9431
|
+
yield* Effect.forEach(revokedSessionIds, (revokedSessionId) => emitRemoved(revokedSessionId), {
|
|
9432
|
+
concurrency: "unbounded",
|
|
9433
|
+
discard: true
|
|
9434
|
+
});
|
|
9435
|
+
}
|
|
9436
|
+
return revokedSessionIds.length;
|
|
9437
|
+
});
|
|
9438
|
+
return SessionStore.of({
|
|
9439
|
+
cookieName,
|
|
9440
|
+
issue,
|
|
9441
|
+
verify,
|
|
9442
|
+
issueWebSocketToken,
|
|
9443
|
+
verifyWebSocketToken,
|
|
9444
|
+
listActive,
|
|
9445
|
+
get streamChanges() {
|
|
9446
|
+
return Stream.fromPubSub(changesPubSub);
|
|
9447
|
+
},
|
|
9448
|
+
revoke,
|
|
9449
|
+
revokeAllExcept,
|
|
9450
|
+
markConnected,
|
|
9451
|
+
markDisconnected
|
|
9106
9452
|
});
|
|
9107
9453
|
});
|
|
9108
|
-
const layer$61 = Layer.effect(
|
|
9454
|
+
const layer$61 = Layer.effect(SessionStore, make$65).pipe(Layer.provideMerge(layer$63));
|
|
9109
9455
|
//#endregion
|
|
9110
|
-
//#region src/persistence/
|
|
9111
|
-
const
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
Schema$1.
|
|
9120
|
-
sessionId: AuthSessionId,
|
|
9121
|
-
subject: Schema$1.String,
|
|
9122
|
-
scopes: AuthEnvironmentScopes,
|
|
9123
|
-
method: ServerAuthSessionMethod,
|
|
9124
|
-
client: AuthSessionClientMetadataRecord,
|
|
9125
|
-
issuedAt: Schema$1.DateTimeUtcFromString,
|
|
9456
|
+
//#region src/persistence/AuthPairingLinks.ts
|
|
9457
|
+
const AuthPairingLinkRecord = Schema$1.Struct({
|
|
9458
|
+
id: Schema$1.String,
|
|
9459
|
+
credential: Schema$1.String,
|
|
9460
|
+
method: Schema$1.Literals(["desktop-bootstrap", "one-time-token"]),
|
|
9461
|
+
scopes: Schema$1.fromJsonString(AuthEnvironmentScopes),
|
|
9462
|
+
subject: Schema$1.String,
|
|
9463
|
+
label: Schema$1.NullOr(Schema$1.String),
|
|
9464
|
+
proofKeyThumbprint: Schema$1.NullOr(Schema$1.String),
|
|
9465
|
+
createdAt: Schema$1.DateTimeUtcFromString,
|
|
9126
9466
|
expiresAt: Schema$1.DateTimeUtcFromString,
|
|
9127
|
-
|
|
9467
|
+
consumedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
|
|
9128
9468
|
revokedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString)
|
|
9129
9469
|
});
|
|
9130
|
-
const
|
|
9131
|
-
|
|
9132
|
-
|
|
9470
|
+
const CreateAuthPairingLinkInput = Schema$1.Struct({
|
|
9471
|
+
id: Schema$1.String,
|
|
9472
|
+
credential: Schema$1.String,
|
|
9473
|
+
method: Schema$1.Literals(["desktop-bootstrap", "one-time-token"]),
|
|
9133
9474
|
scopes: AuthEnvironmentScopes,
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9475
|
+
subject: Schema$1.String,
|
|
9476
|
+
label: Schema$1.NullOr(Schema$1.String),
|
|
9477
|
+
proofKeyThumbprint: Schema$1.NullOr(Schema$1.String),
|
|
9478
|
+
createdAt: Schema$1.DateTimeUtcFromString,
|
|
9137
9479
|
expiresAt: Schema$1.DateTimeUtcFromString
|
|
9138
9480
|
});
|
|
9139
|
-
const
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9481
|
+
const ConsumeAuthPairingLinkInput = Schema$1.Struct({
|
|
9482
|
+
credential: Schema$1.String,
|
|
9483
|
+
proofKeyThumbprint: Schema$1.NullOr(Schema$1.String),
|
|
9484
|
+
consumedAt: Schema$1.DateTimeUtcFromString,
|
|
9485
|
+
now: Schema$1.DateTimeUtcFromString
|
|
9144
9486
|
});
|
|
9145
|
-
const
|
|
9146
|
-
|
|
9487
|
+
const ListActiveAuthPairingLinksInput = Schema$1.Struct({ now: Schema$1.DateTimeUtcFromString });
|
|
9488
|
+
const RevokeAuthPairingLinkInput = Schema$1.Struct({
|
|
9489
|
+
id: Schema$1.String,
|
|
9147
9490
|
revokedAt: Schema$1.DateTimeUtcFromString
|
|
9148
9491
|
});
|
|
9149
|
-
const
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
var AuthSessionRepository = class extends Context.Service()("@p4code/cli/persistence/AuthSessions/AuthSessionRepository") {};
|
|
9154
|
-
const AuthSessionDbRow = Schema$1.Struct({
|
|
9155
|
-
sessionId: AuthSessionId,
|
|
9156
|
-
subject: Schema$1.String,
|
|
9157
|
-
scopes: Schema$1.fromJsonString(AuthEnvironmentScopes),
|
|
9158
|
-
method: ServerAuthSessionMethod,
|
|
9159
|
-
clientLabel: Schema$1.NullOr(Schema$1.String),
|
|
9160
|
-
clientIpAddress: Schema$1.NullOr(Schema$1.String),
|
|
9161
|
-
clientUserAgent: Schema$1.NullOr(Schema$1.String),
|
|
9162
|
-
clientDeviceType: Schema$1.Literals([
|
|
9163
|
-
"desktop",
|
|
9164
|
-
"mobile",
|
|
9165
|
-
"tablet",
|
|
9166
|
-
"bot",
|
|
9167
|
-
"unknown"
|
|
9168
|
-
]),
|
|
9169
|
-
clientOs: Schema$1.NullOr(Schema$1.String),
|
|
9170
|
-
clientBrowser: Schema$1.NullOr(Schema$1.String),
|
|
9171
|
-
issuedAt: Schema$1.DateTimeUtcFromString,
|
|
9172
|
-
expiresAt: Schema$1.DateTimeUtcFromString,
|
|
9173
|
-
lastConnectedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
|
|
9174
|
-
revokedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString)
|
|
9175
|
-
});
|
|
9176
|
-
const AuthSessionRawDbRow = Schema$1.Struct({
|
|
9177
|
-
sessionId: Schema$1.String,
|
|
9178
|
-
subject: Schema$1.Unknown,
|
|
9179
|
-
scopes: Schema$1.Unknown,
|
|
9492
|
+
const GetAuthPairingLinkByCredentialInput = Schema$1.Struct({ credential: Schema$1.String });
|
|
9493
|
+
const AuthPairingLinkRawDbRow = Schema$1.Struct({
|
|
9494
|
+
id: Schema$1.String,
|
|
9495
|
+
credential: Schema$1.Unknown,
|
|
9180
9496
|
method: Schema$1.Unknown,
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
clientBrowser: Schema$1.Unknown,
|
|
9187
|
-
issuedAt: Schema$1.Unknown,
|
|
9497
|
+
scopes: Schema$1.Unknown,
|
|
9498
|
+
subject: Schema$1.Unknown,
|
|
9499
|
+
label: Schema$1.Unknown,
|
|
9500
|
+
proofKeyThumbprint: Schema$1.Unknown,
|
|
9501
|
+
createdAt: Schema$1.Unknown,
|
|
9188
9502
|
expiresAt: Schema$1.Unknown,
|
|
9189
|
-
|
|
9503
|
+
consumedAt: Schema$1.Unknown,
|
|
9190
9504
|
revokedAt: Schema$1.Unknown
|
|
9191
9505
|
});
|
|
9192
|
-
const
|
|
9193
|
-
|
|
9194
|
-
return {
|
|
9195
|
-
sessionId: row.sessionId,
|
|
9196
|
-
subject: row.subject,
|
|
9197
|
-
scopes: row.scopes,
|
|
9198
|
-
method: row.method,
|
|
9199
|
-
client: {
|
|
9200
|
-
label: row.clientLabel,
|
|
9201
|
-
ipAddress: row.clientIpAddress,
|
|
9202
|
-
userAgent: row.clientUserAgent,
|
|
9203
|
-
deviceType: row.clientDeviceType,
|
|
9204
|
-
os: row.clientOs,
|
|
9205
|
-
browser: row.clientBrowser
|
|
9206
|
-
},
|
|
9207
|
-
issuedAt: row.issuedAt,
|
|
9208
|
-
expiresAt: row.expiresAt,
|
|
9209
|
-
lastConnectedAt: row.lastConnectedAt,
|
|
9210
|
-
revokedAt: row.revokedAt
|
|
9211
|
-
};
|
|
9212
|
-
}
|
|
9506
|
+
const decodeAuthPairingLinkDbRow = Schema$1.decodeUnknownEffect(AuthPairingLinkRecord);
|
|
9507
|
+
var AuthPairingLinkRepository = class extends Context.Service()("@p4code/cli/persistence/AuthPairingLinks/AuthPairingLinkRepository") {};
|
|
9213
9508
|
function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlation) {
|
|
9214
9509
|
return (cause) => Schema$1.isSchemaError(cause) ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause, correlation) : new PersistenceSqlError({
|
|
9215
9510
|
operation: sqlOperation,
|
|
@@ -9219,668 +9514,433 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
|
|
|
9219
9514
|
}
|
|
9220
9515
|
const make$64 = Effect.gen(function* () {
|
|
9221
9516
|
const sql = yield* SqlClient.SqlClient;
|
|
9222
|
-
const
|
|
9223
|
-
Request:
|
|
9517
|
+
const createPairingLinkRow = SqlSchema.void({
|
|
9518
|
+
Request: CreateAuthPairingLinkInput,
|
|
9224
9519
|
execute: (input) => sql`
|
|
9225
|
-
INSERT INTO
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
scopes,
|
|
9520
|
+
INSERT INTO auth_pairing_links (
|
|
9521
|
+
id,
|
|
9522
|
+
credential,
|
|
9229
9523
|
method,
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
client_browser,
|
|
9236
|
-
issued_at,
|
|
9524
|
+
scopes,
|
|
9525
|
+
subject,
|
|
9526
|
+
label,
|
|
9527
|
+
proof_key_thumbprint,
|
|
9528
|
+
created_at,
|
|
9237
9529
|
expires_at,
|
|
9530
|
+
consumed_at,
|
|
9238
9531
|
revoked_at
|
|
9239
9532
|
)
|
|
9240
9533
|
VALUES (
|
|
9241
|
-
${input.
|
|
9242
|
-
${input.
|
|
9243
|
-
${JSON.stringify(input.scopes)},
|
|
9534
|
+
${input.id},
|
|
9535
|
+
${input.credential},
|
|
9244
9536
|
${input.method},
|
|
9245
|
-
${input.
|
|
9246
|
-
${input.
|
|
9247
|
-
${input.
|
|
9248
|
-
${input.
|
|
9249
|
-
${input.
|
|
9250
|
-
${input.client.browser},
|
|
9251
|
-
${input.issuedAt},
|
|
9537
|
+
${JSON.stringify(input.scopes)},
|
|
9538
|
+
${input.subject},
|
|
9539
|
+
${input.label},
|
|
9540
|
+
${input.proofKeyThumbprint},
|
|
9541
|
+
${input.createdAt},
|
|
9252
9542
|
${input.expiresAt},
|
|
9543
|
+
NULL,
|
|
9253
9544
|
NULL
|
|
9254
9545
|
)
|
|
9255
9546
|
`
|
|
9256
9547
|
});
|
|
9257
|
-
const
|
|
9258
|
-
Request:
|
|
9259
|
-
Result:
|
|
9260
|
-
execute: ({
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9548
|
+
const consumeAvailablePairingLinkRow = SqlSchema.findOneOption({
|
|
9549
|
+
Request: ConsumeAuthPairingLinkInput,
|
|
9550
|
+
Result: AuthPairingLinkRawDbRow,
|
|
9551
|
+
execute: ({ credential, proofKeyThumbprint, consumedAt, now }) => sql`
|
|
9552
|
+
UPDATE auth_pairing_links
|
|
9553
|
+
SET consumed_at = ${consumedAt}
|
|
9554
|
+
WHERE credential = ${credential}
|
|
9555
|
+
AND revoked_at IS NULL
|
|
9556
|
+
AND consumed_at IS NULL
|
|
9557
|
+
AND expires_at > ${now}
|
|
9558
|
+
AND (
|
|
9559
|
+
proof_key_thumbprint IS NULL
|
|
9560
|
+
OR proof_key_thumbprint = ${proofKeyThumbprint}
|
|
9561
|
+
)
|
|
9562
|
+
RETURNING
|
|
9563
|
+
id AS "id",
|
|
9564
|
+
credential AS "credential",
|
|
9265
9565
|
method AS "method",
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
client_browser AS "clientBrowser",
|
|
9272
|
-
issued_at AS "issuedAt",
|
|
9566
|
+
scopes AS "scopes",
|
|
9567
|
+
subject AS "subject",
|
|
9568
|
+
label AS "label",
|
|
9569
|
+
proof_key_thumbprint AS "proofKeyThumbprint",
|
|
9570
|
+
created_at AS "createdAt",
|
|
9273
9571
|
expires_at AS "expiresAt",
|
|
9274
|
-
|
|
9572
|
+
consumed_at AS "consumedAt",
|
|
9275
9573
|
revoked_at AS "revokedAt"
|
|
9276
|
-
FROM auth_sessions
|
|
9277
|
-
WHERE session_id = ${sessionId}
|
|
9278
9574
|
`
|
|
9279
9575
|
});
|
|
9280
|
-
const
|
|
9281
|
-
Request:
|
|
9282
|
-
Result:
|
|
9576
|
+
const listActivePairingLinkRows = SqlSchema.findAll({
|
|
9577
|
+
Request: ListActiveAuthPairingLinksInput,
|
|
9578
|
+
Result: AuthPairingLinkRawDbRow,
|
|
9283
9579
|
execute: ({ now }) => sql`
|
|
9284
9580
|
SELECT
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
scopes AS "scopes",
|
|
9581
|
+
id AS "id",
|
|
9582
|
+
credential AS "credential",
|
|
9288
9583
|
method AS "method",
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
client_browser AS "clientBrowser",
|
|
9295
|
-
issued_at AS "issuedAt",
|
|
9584
|
+
scopes AS "scopes",
|
|
9585
|
+
subject AS "subject",
|
|
9586
|
+
label AS "label",
|
|
9587
|
+
proof_key_thumbprint AS "proofKeyThumbprint",
|
|
9588
|
+
created_at AS "createdAt",
|
|
9296
9589
|
expires_at AS "expiresAt",
|
|
9297
|
-
|
|
9590
|
+
consumed_at AS "consumedAt",
|
|
9298
9591
|
revoked_at AS "revokedAt"
|
|
9299
|
-
FROM
|
|
9592
|
+
FROM auth_pairing_links
|
|
9300
9593
|
WHERE revoked_at IS NULL
|
|
9594
|
+
AND consumed_at IS NULL
|
|
9301
9595
|
AND expires_at > ${now}
|
|
9302
|
-
ORDER BY
|
|
9303
|
-
`
|
|
9304
|
-
});
|
|
9305
|
-
const setLastConnectedAtRow = SqlSchema.void({
|
|
9306
|
-
Request: SetAuthSessionLastConnectedAtInput,
|
|
9307
|
-
execute: ({ sessionId, lastConnectedAt }) => sql`
|
|
9308
|
-
UPDATE auth_sessions
|
|
9309
|
-
SET last_connected_at = ${lastConnectedAt}
|
|
9310
|
-
WHERE session_id = ${sessionId}
|
|
9311
|
-
AND revoked_at IS NULL
|
|
9596
|
+
ORDER BY created_at DESC, id DESC
|
|
9312
9597
|
`
|
|
9313
9598
|
});
|
|
9314
|
-
const
|
|
9315
|
-
Request:
|
|
9316
|
-
Result: Schema$1.Struct({
|
|
9317
|
-
execute: ({
|
|
9318
|
-
UPDATE
|
|
9599
|
+
const revokePairingLinkRow = SqlSchema.findAll({
|
|
9600
|
+
Request: RevokeAuthPairingLinkInput,
|
|
9601
|
+
Result: Schema$1.Struct({ id: Schema$1.String }),
|
|
9602
|
+
execute: ({ id, revokedAt }) => sql`
|
|
9603
|
+
UPDATE auth_pairing_links
|
|
9319
9604
|
SET revoked_at = ${revokedAt}
|
|
9320
|
-
WHERE
|
|
9605
|
+
WHERE id = ${id}
|
|
9321
9606
|
AND revoked_at IS NULL
|
|
9322
|
-
|
|
9607
|
+
AND consumed_at IS NULL
|
|
9608
|
+
RETURNING id AS "id"
|
|
9323
9609
|
`
|
|
9324
9610
|
});
|
|
9325
|
-
const
|
|
9326
|
-
Request:
|
|
9327
|
-
Result:
|
|
9328
|
-
execute: ({
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
|
|
9332
|
-
|
|
9333
|
-
|
|
9611
|
+
const getPairingLinkRowByCredential = SqlSchema.findOneOption({
|
|
9612
|
+
Request: GetAuthPairingLinkByCredentialInput,
|
|
9613
|
+
Result: AuthPairingLinkRawDbRow,
|
|
9614
|
+
execute: ({ credential }) => sql`
|
|
9615
|
+
SELECT
|
|
9616
|
+
id AS "id",
|
|
9617
|
+
credential AS "credential",
|
|
9618
|
+
method AS "method",
|
|
9619
|
+
scopes AS "scopes",
|
|
9620
|
+
subject AS "subject",
|
|
9621
|
+
label AS "label",
|
|
9622
|
+
proof_key_thumbprint AS "proofKeyThumbprint",
|
|
9623
|
+
created_at AS "createdAt",
|
|
9624
|
+
expires_at AS "expiresAt",
|
|
9625
|
+
consumed_at AS "consumedAt",
|
|
9626
|
+
revoked_at AS "revokedAt"
|
|
9627
|
+
FROM auth_pairing_links
|
|
9628
|
+
WHERE credential = ${credential}
|
|
9334
9629
|
`
|
|
9335
9630
|
});
|
|
9336
|
-
const create = (input) =>
|
|
9337
|
-
const
|
|
9631
|
+
const create = (input) => createPairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.create:query", "AuthPairingLinkRepository.create:encodeRequest", { pairingLinkId: input.id })));
|
|
9632
|
+
const consumeAvailable = (input) => consumeAvailablePairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.consumeAvailable:query", "AuthPairingLinkRepository.consumeAvailable:decodeRow")), Effect.flatMap((rowOption) => Option.match(rowOption, {
|
|
9338
9633
|
onNone: () => Effect.succeed(Option.none()),
|
|
9339
|
-
onSome: (row) =>
|
|
9634
|
+
onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.consumeAvailable:decodeRow", cause, { pairingLinkId: row.id })), Effect.map(Option.some))
|
|
9635
|
+
})));
|
|
9636
|
+
const listActive = (input) => listActivePairingLinkRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.listActive:query", "AuthPairingLinkRepository.listActive:decodeRows")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.listActive:decodeRows", cause, { pairingLinkId: row.id }))))));
|
|
9637
|
+
const revoke = (input) => revokePairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.revoke:query", "AuthPairingLinkRepository.revoke:decodeRows", { pairingLinkId: input.id })), Effect.map((rows) => rows.length > 0));
|
|
9638
|
+
const getByCredential = (input) => getPairingLinkRowByCredential(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.getByCredential:query", "AuthPairingLinkRepository.getByCredential:decodeRow")), Effect.flatMap((rowOption) => Option.match(rowOption, {
|
|
9639
|
+
onNone: () => Effect.succeed(Option.none()),
|
|
9640
|
+
onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.getByCredential:decodeRow", cause, { pairingLinkId: row.id })), Effect.map(Option.some))
|
|
9340
9641
|
})));
|
|
9341
|
-
const listActive = (input) => listActiveSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.listActive:query", "AuthSessionRepository.listActive:decodeRows")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeAuthSessionDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthSessionRepository.listActive:decodeRows", cause, { sessionId: row.sessionId })), Effect.map(toAuthSessionRecord)))));
|
|
9342
|
-
const revoke = (input) => revokeSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.revoke:query", "AuthSessionRepository.revoke:decodeRows", { sessionId: input.sessionId })), Effect.map((rows) => rows.length > 0));
|
|
9343
|
-
const revokeAllExcept = (input) => revokeOtherSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.revokeAllExcept:query", "AuthSessionRepository.revokeAllExcept:decodeRows", { currentSessionId: input.currentSessionId })), Effect.map((rows) => rows.map((row) => row.sessionId)));
|
|
9344
|
-
const setLastConnectedAt = (input) => setLastConnectedAtRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.setLastConnectedAt:query", "AuthSessionRepository.setLastConnectedAt:encodeRequest", { sessionId: input.sessionId })));
|
|
9345
9642
|
return {
|
|
9346
9643
|
create,
|
|
9347
|
-
|
|
9644
|
+
consumeAvailable,
|
|
9348
9645
|
listActive,
|
|
9349
9646
|
revoke,
|
|
9350
|
-
|
|
9351
|
-
setLastConnectedAt
|
|
9647
|
+
getByCredential
|
|
9352
9648
|
};
|
|
9353
9649
|
});
|
|
9354
|
-
const layer$60 = Layer.effect(
|
|
9650
|
+
const layer$60 = Layer.effect(AuthPairingLinkRepository, make$64);
|
|
9355
9651
|
//#endregion
|
|
9356
|
-
//#region src/auth/
|
|
9357
|
-
var
|
|
9358
|
-
get message() {
|
|
9359
|
-
return "Malformed session token.";
|
|
9360
|
-
}
|
|
9361
|
-
};
|
|
9362
|
-
var InvalidSessionTokenSignatureError = class extends Schema$1.TaggedErrorClass()("InvalidSessionTokenSignatureError", {}) {
|
|
9363
|
-
get message() {
|
|
9364
|
-
return "Invalid session token signature.";
|
|
9365
|
-
}
|
|
9366
|
-
};
|
|
9367
|
-
var InvalidSessionTokenPayloadError = class extends Schema$1.TaggedErrorClass()("InvalidSessionTokenPayloadError", { cause: Schema$1.Defect() }) {
|
|
9368
|
-
get message() {
|
|
9369
|
-
return "Invalid session token payload.";
|
|
9370
|
-
}
|
|
9371
|
-
};
|
|
9372
|
-
var SessionTokenExpiredError = class extends Schema$1.TaggedErrorClass()("SessionTokenExpiredError", {
|
|
9373
|
-
sessionId: AuthSessionId,
|
|
9374
|
-
expiresAt: Schema$1.DateTimeUtc,
|
|
9375
|
-
observedAt: Schema$1.DateTimeUtc
|
|
9376
|
-
}) {
|
|
9377
|
-
get message() {
|
|
9378
|
-
return "Session token expired.";
|
|
9379
|
-
}
|
|
9380
|
-
};
|
|
9381
|
-
var UnknownSessionTokenError = class extends Schema$1.TaggedErrorClass()("UnknownSessionTokenError", { sessionId: AuthSessionId }) {
|
|
9382
|
-
get message() {
|
|
9383
|
-
return "Unknown session token.";
|
|
9384
|
-
}
|
|
9385
|
-
};
|
|
9386
|
-
var SessionTokenRevokedError = class extends Schema$1.TaggedErrorClass()("SessionTokenRevokedError", {
|
|
9387
|
-
sessionId: AuthSessionId,
|
|
9388
|
-
revokedAt: Schema$1.DateTimeUtc
|
|
9389
|
-
}) {
|
|
9390
|
-
get message() {
|
|
9391
|
-
return "Session token revoked.";
|
|
9392
|
-
}
|
|
9393
|
-
};
|
|
9394
|
-
var InvalidSessionExpirationClaimError = class extends Schema$1.TaggedErrorClass()("InvalidSessionExpirationClaimError", {
|
|
9395
|
-
sessionId: AuthSessionId,
|
|
9396
|
-
expirationClaim: Schema$1.Number
|
|
9397
|
-
}) {
|
|
9398
|
-
get message() {
|
|
9399
|
-
return "Invalid `exp` claim";
|
|
9400
|
-
}
|
|
9401
|
-
};
|
|
9402
|
-
var MalformedWebSocketTokenError = class extends Schema$1.TaggedErrorClass()("MalformedWebSocketTokenError", {}) {
|
|
9403
|
-
get message() {
|
|
9404
|
-
return "Malformed websocket token.";
|
|
9405
|
-
}
|
|
9406
|
-
};
|
|
9407
|
-
var InvalidWebSocketTokenSignatureError = class extends Schema$1.TaggedErrorClass()("InvalidWebSocketTokenSignatureError", {}) {
|
|
9408
|
-
get message() {
|
|
9409
|
-
return "Invalid websocket token signature.";
|
|
9410
|
-
}
|
|
9411
|
-
};
|
|
9412
|
-
var InvalidWebSocketTokenPayloadError = class extends Schema$1.TaggedErrorClass()("InvalidWebSocketTokenPayloadError", { cause: Schema$1.Defect() }) {
|
|
9413
|
-
get message() {
|
|
9414
|
-
return "Invalid websocket token payload.";
|
|
9415
|
-
}
|
|
9416
|
-
};
|
|
9417
|
-
var WebSocketTokenExpiredError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenExpiredError", {
|
|
9418
|
-
sessionId: AuthSessionId,
|
|
9419
|
-
expiresAt: Schema$1.DateTimeUtc,
|
|
9420
|
-
observedAt: Schema$1.DateTimeUtc
|
|
9421
|
-
}) {
|
|
9652
|
+
//#region src/auth/PairingGrantStore.ts
|
|
9653
|
+
var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
|
|
9422
9654
|
get message() {
|
|
9423
|
-
return "
|
|
9655
|
+
return "Unknown bootstrap credential.";
|
|
9424
9656
|
}
|
|
9425
9657
|
};
|
|
9426
|
-
var
|
|
9658
|
+
var ExpiredBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("ExpiredBootstrapCredentialError", {}) {
|
|
9427
9659
|
get message() {
|
|
9428
|
-
return "
|
|
9660
|
+
return "Bootstrap credential expired.";
|
|
9429
9661
|
}
|
|
9430
9662
|
};
|
|
9431
|
-
var
|
|
9432
|
-
sessionId: AuthSessionId,
|
|
9433
|
-
expiresAt: Schema$1.DateTimeUtc,
|
|
9434
|
-
observedAt: Schema$1.DateTimeUtc
|
|
9435
|
-
}) {
|
|
9663
|
+
var BootstrapCredentialProofKeyMismatchError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialProofKeyMismatchError", {}) {
|
|
9436
9664
|
get message() {
|
|
9437
|
-
return "
|
|
9665
|
+
return "Bootstrap credential proof key mismatch.";
|
|
9438
9666
|
}
|
|
9439
9667
|
};
|
|
9440
|
-
var
|
|
9441
|
-
sessionId: AuthSessionId,
|
|
9442
|
-
revokedAt: Schema$1.DateTimeUtc
|
|
9443
|
-
}) {
|
|
9668
|
+
var UnavailableBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnavailableBootstrapCredentialError", {}) {
|
|
9444
9669
|
get message() {
|
|
9445
|
-
return "
|
|
9670
|
+
return "Bootstrap credential is no longer available.";
|
|
9446
9671
|
}
|
|
9447
9672
|
};
|
|
9448
|
-
const
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
UnknownSessionTokenError,
|
|
9454
|
-
SessionTokenRevokedError,
|
|
9455
|
-
InvalidSessionExpirationClaimError,
|
|
9456
|
-
MalformedWebSocketTokenError,
|
|
9457
|
-
InvalidWebSocketTokenSignatureError,
|
|
9458
|
-
InvalidWebSocketTokenPayloadError,
|
|
9459
|
-
WebSocketTokenExpiredError,
|
|
9460
|
-
UnknownWebSocketSessionError,
|
|
9461
|
-
WebSocketSessionExpiredError,
|
|
9462
|
-
WebSocketSessionRevokedError
|
|
9673
|
+
const BootstrapCredentialInvalidError = Schema$1.Union([
|
|
9674
|
+
UnknownBootstrapCredentialError,
|
|
9675
|
+
ExpiredBootstrapCredentialError,
|
|
9676
|
+
BootstrapCredentialProofKeyMismatchError,
|
|
9677
|
+
UnavailableBootstrapCredentialError
|
|
9463
9678
|
]);
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
var SessionClaimsEncodingError = class extends Schema$1.TaggedErrorClass()("SessionClaimsEncodingError", {
|
|
9467
|
-
sessionId: AuthSessionId,
|
|
9468
|
-
operation: Schema$1.Literals(["encode_session_claims", "encode_websocket_claims"]),
|
|
9469
|
-
...sessionCredentialInternalErrorContext
|
|
9470
|
-
}) {
|
|
9471
|
-
get message() {
|
|
9472
|
-
return "Failed to encode claims";
|
|
9473
|
-
}
|
|
9474
|
-
};
|
|
9475
|
-
var SessionCredentialIssueError = class extends Schema$1.TaggedErrorClass()("SessionCredentialIssueError", {
|
|
9476
|
-
sessionId: Schema$1.optional(AuthSessionId),
|
|
9477
|
-
...sessionCredentialInternalErrorContext
|
|
9478
|
-
}) {
|
|
9679
|
+
Schema$1.is(BootstrapCredentialInvalidError);
|
|
9680
|
+
var ActivePairingLinksLoadError = class extends Schema$1.TaggedErrorClass()("ActivePairingLinksLoadError", { cause: Schema$1.Defect() }) {
|
|
9479
9681
|
get message() {
|
|
9480
|
-
return "Failed to
|
|
9682
|
+
return "Failed to load active pairing links.";
|
|
9481
9683
|
}
|
|
9482
9684
|
};
|
|
9483
|
-
var
|
|
9484
|
-
|
|
9485
|
-
|
|
9685
|
+
var PairingLinkRevokeError = class extends Schema$1.TaggedErrorClass()("PairingLinkRevokeError", {
|
|
9686
|
+
pairingLinkId: Schema$1.String,
|
|
9687
|
+
cause: Schema$1.Defect()
|
|
9486
9688
|
}) {
|
|
9487
9689
|
get message() {
|
|
9488
|
-
return
|
|
9690
|
+
return `Failed to revoke pairing link '${this.pairingLinkId}'.`;
|
|
9489
9691
|
}
|
|
9490
9692
|
};
|
|
9491
|
-
var
|
|
9492
|
-
|
|
9493
|
-
|
|
9693
|
+
var PairingCredentialIssueError = class extends Schema$1.TaggedErrorClass()("PairingCredentialIssueError", {
|
|
9694
|
+
pairingLinkId: Schema$1.String,
|
|
9695
|
+
subject: Schema$1.String,
|
|
9696
|
+
label: Schema$1.optional(Schema$1.String),
|
|
9697
|
+
cause: Schema$1.Defect()
|
|
9494
9698
|
}) {
|
|
9495
9699
|
get message() {
|
|
9496
|
-
return
|
|
9700
|
+
return `Failed to issue pairing credential '${this.pairingLinkId}' for '${this.subject}'.`;
|
|
9497
9701
|
}
|
|
9498
9702
|
};
|
|
9499
|
-
var
|
|
9500
|
-
|
|
9501
|
-
|
|
9703
|
+
var PairingCredentialRandomGenerationError = class extends Schema$1.TaggedErrorClass()("PairingCredentialRandomGenerationError", {
|
|
9704
|
+
operation: Schema$1.Literals(["generate-id", "generate-token"]),
|
|
9705
|
+
cause: Schema$1.Defect()
|
|
9502
9706
|
}) {
|
|
9503
9707
|
get message() {
|
|
9504
|
-
return
|
|
9708
|
+
return `Failed to generate pairing credential data during '${this.operation}'.`;
|
|
9505
9709
|
}
|
|
9506
9710
|
};
|
|
9507
|
-
var
|
|
9711
|
+
var BootstrapCredentialConsumeError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialConsumeError", { cause: Schema$1.Defect() }) {
|
|
9508
9712
|
get message() {
|
|
9509
|
-
return "Failed to
|
|
9713
|
+
return "Failed to consume bootstrap credential.";
|
|
9510
9714
|
}
|
|
9511
9715
|
};
|
|
9512
|
-
var
|
|
9513
|
-
sessionId: AuthSessionId,
|
|
9514
|
-
...sessionCredentialInternalErrorContext
|
|
9515
|
-
}) {
|
|
9716
|
+
var BootstrapCredentialConsumeAvailableError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialConsumeAvailableError", { cause: Schema$1.Defect() }) {
|
|
9516
9717
|
get message() {
|
|
9517
|
-
return "Failed to
|
|
9718
|
+
return "Failed to atomically consume an available bootstrap credential.";
|
|
9518
9719
|
}
|
|
9519
9720
|
};
|
|
9520
|
-
var
|
|
9521
|
-
currentSessionId: AuthSessionId,
|
|
9522
|
-
...sessionCredentialInternalErrorContext
|
|
9523
|
-
}) {
|
|
9721
|
+
var BootstrapCredentialLookupError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialLookupError", { cause: Schema$1.Defect() }) {
|
|
9524
9722
|
get message() {
|
|
9525
|
-
return "Failed to
|
|
9723
|
+
return "Failed to look up bootstrap credential state.";
|
|
9526
9724
|
}
|
|
9527
9725
|
};
|
|
9528
|
-
const
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
Schema$1.
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
const
|
|
9543
|
-
const
|
|
9544
|
-
const
|
|
9545
|
-
const
|
|
9546
|
-
|
|
9547
|
-
kind: Schema$1.Literal("session"),
|
|
9548
|
-
sid: AuthSessionId,
|
|
9549
|
-
sub: Schema$1.String,
|
|
9550
|
-
scopes: AuthEnvironmentScopes,
|
|
9551
|
-
method: Schema$1.Literals(["browser-session-cookie", "bearer-access-token"]),
|
|
9552
|
-
jkt: Schema$1.optionalKey(Schema$1.String),
|
|
9553
|
-
iat: Schema$1.Number,
|
|
9554
|
-
exp: Schema$1.Number
|
|
9555
|
-
});
|
|
9556
|
-
const WebSocketClaims = Schema$1.Struct({
|
|
9557
|
-
v: Schema$1.Literal(1),
|
|
9558
|
-
kind: Schema$1.Literal("websocket"),
|
|
9559
|
-
sid: AuthSessionId,
|
|
9560
|
-
iat: Schema$1.Number,
|
|
9561
|
-
exp: Schema$1.Number
|
|
9562
|
-
});
|
|
9563
|
-
const decodeSessionClaims = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(SessionClaims));
|
|
9564
|
-
const decodeWebSocketClaims = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(WebSocketClaims));
|
|
9565
|
-
function createDefaultClientMetadata() {
|
|
9566
|
-
return { deviceType: "unknown" };
|
|
9567
|
-
}
|
|
9568
|
-
function toClientMetadata(record) {
|
|
9569
|
-
return {
|
|
9570
|
-
...record.label ? { label: record.label } : {},
|
|
9571
|
-
...record.ipAddress ? { ipAddress: record.ipAddress } : {},
|
|
9572
|
-
...record.userAgent ? { userAgent: record.userAgent } : {},
|
|
9573
|
-
deviceType: record.deviceType,
|
|
9574
|
-
...record.os ? { os: record.os } : {},
|
|
9575
|
-
...record.browser ? { browser: record.browser } : {}
|
|
9576
|
-
};
|
|
9577
|
-
}
|
|
9578
|
-
function toAuthClientSession(input) {
|
|
9579
|
-
return {
|
|
9580
|
-
...input,
|
|
9581
|
-
current: false
|
|
9582
|
-
};
|
|
9583
|
-
}
|
|
9726
|
+
const BootstrapCredentialInternalError = Schema$1.Union([
|
|
9727
|
+
ActivePairingLinksLoadError,
|
|
9728
|
+
PairingLinkRevokeError,
|
|
9729
|
+
PairingCredentialIssueError,
|
|
9730
|
+
PairingCredentialRandomGenerationError,
|
|
9731
|
+
BootstrapCredentialConsumeError,
|
|
9732
|
+
BootstrapCredentialConsumeAvailableError,
|
|
9733
|
+
BootstrapCredentialLookupError
|
|
9734
|
+
]);
|
|
9735
|
+
const isBootstrapCredentialInternalError = Schema$1.is(BootstrapCredentialInternalError);
|
|
9736
|
+
const BootstrapCredentialError = Schema$1.Union([BootstrapCredentialInvalidError, BootstrapCredentialInternalError]);
|
|
9737
|
+
Schema$1.is(BootstrapCredentialError);
|
|
9738
|
+
var PairingGrantStore = class extends Context.Service()("@p4code/cli/auth/PairingGrantStore") {};
|
|
9739
|
+
const DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES = Duration.minutes(5);
|
|
9740
|
+
const DESKTOP_BOOTSTRAP_TTL_HOURS = Duration.hours(24);
|
|
9741
|
+
const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
|
|
9742
|
+
const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
9743
|
+
const PAIRING_TOKEN_LENGTH = 12;
|
|
9744
|
+
const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
|
|
9584
9745
|
const make$63 = Effect.gen(function* () {
|
|
9585
9746
|
const crypto = yield* Crypto.Crypto;
|
|
9586
|
-
const
|
|
9587
|
-
const
|
|
9588
|
-
const
|
|
9589
|
-
const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME$1, 32);
|
|
9590
|
-
const connectedSessionsRef = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
9747
|
+
const config = yield* ServerConfig$1;
|
|
9748
|
+
const pairingLinks = yield* AuthPairingLinkRepository;
|
|
9749
|
+
const seededGrantsRef = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
9591
9750
|
const changesPubSub = yield* PubSub.unbounded();
|
|
9592
|
-
const
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
}).pipe(Effect.asVoid);
|
|
9607
|
-
const loadActiveSession = (sessionId) => Effect.gen(function* () {
|
|
9608
|
-
const row = yield* authSessions.getById({ sessionId });
|
|
9609
|
-
if (Option.isNone(row) || row.value.revokedAt !== null) return Option.none();
|
|
9610
|
-
const connectedSessions = yield* Ref.get(connectedSessionsRef);
|
|
9611
|
-
return Option.some(toAuthClientSession({
|
|
9612
|
-
sessionId: row.value.sessionId,
|
|
9613
|
-
subject: row.value.subject,
|
|
9614
|
-
scopes: row.value.scopes,
|
|
9615
|
-
method: row.value.method,
|
|
9616
|
-
client: toClientMetadata(row.value.client),
|
|
9617
|
-
issuedAt: row.value.issuedAt,
|
|
9618
|
-
expiresAt: row.value.expiresAt,
|
|
9619
|
-
lastConnectedAt: row.value.lastConnectedAt,
|
|
9620
|
-
connected: connectedSessions.has(row.value.sessionId)
|
|
9621
|
-
}));
|
|
9751
|
+
const generatePairingToken = Effect.gen(function* () {
|
|
9752
|
+
let credential = "";
|
|
9753
|
+
while (credential.length < PAIRING_TOKEN_LENGTH) {
|
|
9754
|
+
const bytes = yield* crypto.randomBytes(PAIRING_TOKEN_LENGTH).pipe(Effect.mapError((cause) => new PairingCredentialRandomGenerationError({
|
|
9755
|
+
operation: "generate-token",
|
|
9756
|
+
cause
|
|
9757
|
+
})));
|
|
9758
|
+
for (const byte of bytes) {
|
|
9759
|
+
if (byte >= PAIRING_TOKEN_REJECTION_LIMIT) continue;
|
|
9760
|
+
credential += PAIRING_TOKEN_ALPHABET[byte % 32];
|
|
9761
|
+
if (credential.length === PAIRING_TOKEN_LENGTH) return credential;
|
|
9762
|
+
}
|
|
9763
|
+
}
|
|
9764
|
+
return credential;
|
|
9622
9765
|
});
|
|
9623
|
-
const
|
|
9624
|
-
const next = new Map(current);
|
|
9625
|
-
const wasDisconnected = !next.has(sessionId);
|
|
9626
|
-
next.set(sessionId, (next.get(sessionId) ?? 0) + 1);
|
|
9627
|
-
return [wasDisconnected, next];
|
|
9628
|
-
}).pipe(Effect.flatMap((wasDisconnected) => wasDisconnected ? DateTime.now.pipe(Effect.flatMap((lastConnectedAt) => authSessions.setLastConnectedAt({
|
|
9629
|
-
sessionId,
|
|
9630
|
-
lastConnectedAt
|
|
9631
|
-
}))) : Effect.void), Effect.flatMap(() => loadActiveSession(sessionId)), Effect.flatMap((session) => Option.isSome(session) ? emitUpsert(session.value) : Effect.void), Effect.catchCause((cause) => Effect.logError("Failed to publish connected-session auth update.").pipe(Effect.annotateLogs({
|
|
9632
|
-
sessionId,
|
|
9633
|
-
cause
|
|
9634
|
-
}))), Effect.withSpan("SessionStore.markConnected"));
|
|
9635
|
-
const markDisconnected = (sessionId) => Ref.update(connectedSessionsRef, (current) => {
|
|
9766
|
+
const seedGrant = (credential, grant) => Ref.update(seededGrantsRef, (current) => {
|
|
9636
9767
|
const next = new Map(current);
|
|
9637
|
-
|
|
9638
|
-
if (remaining > 0) next.set(sessionId, remaining);
|
|
9639
|
-
else next.delete(sessionId);
|
|
9768
|
+
next.set(credential, grant);
|
|
9640
9769
|
return next;
|
|
9641
|
-
}).pipe(Effect.flatMap(() => loadActiveSession(sessionId)), Effect.flatMap((session) => Option.isSome(session) ? emitUpsert(session.value) : Effect.void), Effect.catchCause((cause) => Effect.logError("Failed to publish disconnected-session auth update.").pipe(Effect.annotateLogs({
|
|
9642
|
-
sessionId,
|
|
9643
|
-
cause
|
|
9644
|
-
}))), Effect.withSpan("SessionStore.markDisconnected"));
|
|
9645
|
-
const encodeClaims = Schema$1.encodeEffect(Schema$1.fromJsonString(SessionClaims));
|
|
9646
|
-
const issue = Effect.fn("SessionStore.issue")(function* (input) {
|
|
9647
|
-
const sessionId = AuthSessionId.make(yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new SessionCredentialIssueError({ cause }))));
|
|
9648
|
-
const issuedAt = yield* DateTime.now;
|
|
9649
|
-
const expiresAt = DateTime.add(issuedAt, { milliseconds: Duration.toMillis(input?.ttl ?? DEFAULT_SESSION_TTL) });
|
|
9650
|
-
const claims = {
|
|
9651
|
-
v: 1,
|
|
9652
|
-
kind: "session",
|
|
9653
|
-
sid: sessionId,
|
|
9654
|
-
sub: input?.subject ?? "browser",
|
|
9655
|
-
scopes: input?.scopes ?? AuthStandardClientScopes,
|
|
9656
|
-
method: input?.method ?? "browser-session-cookie",
|
|
9657
|
-
...input?.proofKeyThumbprint ? { jkt: input.proofKeyThumbprint } : {},
|
|
9658
|
-
iat: issuedAt.epochMilliseconds,
|
|
9659
|
-
exp: expiresAt.epochMilliseconds
|
|
9660
|
-
};
|
|
9661
|
-
const encodedPayload = yield* encodeClaims(claims).pipe(Effect.map(base64UrlEncode), Effect.mapError((cause) => new SessionCredentialIssueError({
|
|
9662
|
-
sessionId,
|
|
9663
|
-
cause: new SessionClaimsEncodingError({
|
|
9664
|
-
sessionId,
|
|
9665
|
-
operation: "encode_session_claims",
|
|
9666
|
-
cause
|
|
9667
|
-
})
|
|
9668
|
-
})));
|
|
9669
|
-
const signature = signPayload(encodedPayload, signingSecret);
|
|
9670
|
-
const client = input?.client ?? createDefaultClientMetadata();
|
|
9671
|
-
yield* authSessions.create({
|
|
9672
|
-
sessionId,
|
|
9673
|
-
subject: claims.sub,
|
|
9674
|
-
scopes: claims.scopes,
|
|
9675
|
-
method: claims.method,
|
|
9676
|
-
client: {
|
|
9677
|
-
label: client.label ?? null,
|
|
9678
|
-
ipAddress: client.ipAddress ?? null,
|
|
9679
|
-
userAgent: client.userAgent ?? null,
|
|
9680
|
-
deviceType: client.deviceType,
|
|
9681
|
-
os: client.os ?? null,
|
|
9682
|
-
browser: client.browser ?? null
|
|
9683
|
-
},
|
|
9684
|
-
issuedAt,
|
|
9685
|
-
expiresAt
|
|
9686
|
-
}).pipe(Effect.mapError((cause) => new SessionCredentialIssueError({
|
|
9687
|
-
sessionId,
|
|
9688
|
-
cause
|
|
9689
|
-
})));
|
|
9690
|
-
yield* emitUpsert(toAuthClientSession({
|
|
9691
|
-
sessionId,
|
|
9692
|
-
subject: claims.sub,
|
|
9693
|
-
scopes: claims.scopes,
|
|
9694
|
-
method: claims.method,
|
|
9695
|
-
client,
|
|
9696
|
-
issuedAt,
|
|
9697
|
-
expiresAt,
|
|
9698
|
-
lastConnectedAt: null,
|
|
9699
|
-
connected: false
|
|
9700
|
-
}));
|
|
9701
|
-
return {
|
|
9702
|
-
sessionId,
|
|
9703
|
-
token: `${encodedPayload}.${signature}`,
|
|
9704
|
-
method: claims.method,
|
|
9705
|
-
client,
|
|
9706
|
-
expiresAt,
|
|
9707
|
-
scopes: claims.scopes,
|
|
9708
|
-
...claims.jkt ? { proofKeyThumbprint: claims.jkt } : {}
|
|
9709
|
-
};
|
|
9710
9770
|
});
|
|
9711
|
-
const
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9771
|
+
const emitUpsert = (pairingLink) => PubSub.publish(changesPubSub, {
|
|
9772
|
+
type: "pairingLinkUpserted",
|
|
9773
|
+
pairingLink
|
|
9774
|
+
}).pipe(Effect.asVoid);
|
|
9775
|
+
const emitRemoved = (id) => PubSub.publish(changesPubSub, {
|
|
9776
|
+
type: "pairingLinkRemoved",
|
|
9777
|
+
id
|
|
9778
|
+
}).pipe(Effect.asVoid);
|
|
9779
|
+
if (config.desktopBootstrapToken) {
|
|
9780
|
+
const now = yield* DateTime.now;
|
|
9781
|
+
yield* seedGrant(config.desktopBootstrapToken, {
|
|
9782
|
+
method: "desktop-bootstrap",
|
|
9783
|
+
scopes: AuthAdministrativeScopes,
|
|
9784
|
+
subject: DESKTOP_BOOTSTRAP_SUBJECT,
|
|
9785
|
+
expiresAt: DateTime.add(now, { milliseconds: Duration.toMillis(DESKTOP_BOOTSTRAP_TTL_HOURS) }),
|
|
9786
|
+
remainingUses: "unbounded"
|
|
9721
9787
|
});
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9788
|
+
}
|
|
9789
|
+
const listActive = Effect.fn("PairingGrantStore.listActive")(function* () {
|
|
9790
|
+
const now = yield* DateTime.now;
|
|
9791
|
+
return (yield* pairingLinks.listActive({ now })).map((row) => row.label ? {
|
|
9792
|
+
id: row.id,
|
|
9793
|
+
credential: row.credential,
|
|
9794
|
+
scopes: row.scopes,
|
|
9795
|
+
subject: row.subject,
|
|
9796
|
+
label: row.label,
|
|
9797
|
+
createdAt: row.createdAt,
|
|
9798
|
+
expiresAt: row.expiresAt
|
|
9799
|
+
} : {
|
|
9800
|
+
id: row.id,
|
|
9801
|
+
credential: row.credential,
|
|
9802
|
+
scopes: row.scopes,
|
|
9803
|
+
subject: row.subject,
|
|
9804
|
+
createdAt: row.createdAt,
|
|
9805
|
+
expiresAt: row.expiresAt
|
|
9726
9806
|
});
|
|
9727
|
-
|
|
9728
|
-
|
|
9807
|
+
}, Effect.mapError((cause) => new ActivePairingLinksLoadError({ cause })));
|
|
9808
|
+
const revoke = Effect.fn("PairingGrantStore.revoke")(function* (id) {
|
|
9809
|
+
const revokedAt = yield* DateTime.now;
|
|
9810
|
+
const revoked = yield* pairingLinks.revoke({
|
|
9811
|
+
id,
|
|
9812
|
+
revokedAt
|
|
9813
|
+
}).pipe(Effect.mapError((cause) => new PairingLinkRevokeError({
|
|
9814
|
+
pairingLinkId: id,
|
|
9729
9815
|
cause
|
|
9730
9816
|
})));
|
|
9731
|
-
if (
|
|
9732
|
-
|
|
9733
|
-
sessionId: claims.sid,
|
|
9734
|
-
revokedAt: row.value.revokedAt
|
|
9735
|
-
});
|
|
9736
|
-
return {
|
|
9737
|
-
sessionId: claims.sid,
|
|
9738
|
-
token,
|
|
9739
|
-
method: claims.method,
|
|
9740
|
-
client: toClientMetadata(row.value.client),
|
|
9741
|
-
expiresAt: expiresAt.value,
|
|
9742
|
-
subject: claims.sub,
|
|
9743
|
-
scopes: claims.scopes,
|
|
9744
|
-
...claims.jkt ? { proofKeyThumbprint: claims.jkt } : {}
|
|
9745
|
-
};
|
|
9817
|
+
if (revoked) yield* emitRemoved(id);
|
|
9818
|
+
return revoked;
|
|
9746
9819
|
});
|
|
9747
|
-
const
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
const claims = {
|
|
9752
|
-
v: 1,
|
|
9753
|
-
kind: "websocket",
|
|
9754
|
-
sid: sessionId,
|
|
9755
|
-
iat: issuedAt.epochMilliseconds,
|
|
9756
|
-
exp: expiresAt.epochMilliseconds
|
|
9757
|
-
};
|
|
9758
|
-
const encodedPayload = yield* encodeWsClaims(claims).pipe(Effect.map(base64UrlEncode), Effect.mapError((cause) => new WebSocketTokenIssueError({
|
|
9759
|
-
sessionId,
|
|
9760
|
-
cause: new SessionClaimsEncodingError({
|
|
9761
|
-
sessionId,
|
|
9762
|
-
operation: "encode_websocket_claims",
|
|
9763
|
-
cause
|
|
9764
|
-
})
|
|
9820
|
+
const issueOneTimeToken = Effect.fn("PairingGrantStore.issueOneTimeToken")(function* (input) {
|
|
9821
|
+
const id = yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new PairingCredentialRandomGenerationError({
|
|
9822
|
+
operation: "generate-id",
|
|
9823
|
+
cause
|
|
9765
9824
|
})));
|
|
9766
|
-
|
|
9767
|
-
|
|
9825
|
+
const credential = yield* generatePairingToken;
|
|
9826
|
+
const isDevStartupToken = config.devUrl !== void 0 && input?.purpose === "startup";
|
|
9827
|
+
const ttl = input?.ttl ?? (isDevStartupToken ? DEV_STARTUP_TTL_HOURS : DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES);
|
|
9828
|
+
const now = yield* DateTime.now;
|
|
9829
|
+
const expiresAt = DateTime.add(now, { milliseconds: Duration.toMillis(ttl) });
|
|
9830
|
+
const issued = {
|
|
9831
|
+
id,
|
|
9832
|
+
credential,
|
|
9833
|
+
...input?.label ? { label: input.label } : {},
|
|
9834
|
+
...input?.proofKeyThumbprint ? { proofKeyThumbprint: input.proofKeyThumbprint } : {},
|
|
9768
9835
|
expiresAt
|
|
9769
9836
|
};
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
})
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
observedAt
|
|
9786
|
-
});
|
|
9787
|
-
const row = yield* authSessions.getById({ sessionId: claims.sid }).pipe(Effect.mapError((cause) => new WebSocketTokenVerificationError({
|
|
9788
|
-
sessionId: claims.sid,
|
|
9837
|
+
const subject = input?.subject ?? "one-time-token";
|
|
9838
|
+
yield* pairingLinks.create({
|
|
9839
|
+
id,
|
|
9840
|
+
credential,
|
|
9841
|
+
method: "one-time-token",
|
|
9842
|
+
scopes: input?.scopes ?? AuthStandardClientScopes,
|
|
9843
|
+
subject,
|
|
9844
|
+
label: input?.label ?? null,
|
|
9845
|
+
proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
|
|
9846
|
+
createdAt: now,
|
|
9847
|
+
expiresAt
|
|
9848
|
+
}).pipe(Effect.mapError((cause) => new PairingCredentialIssueError({
|
|
9849
|
+
pairingLinkId: id,
|
|
9850
|
+
subject,
|
|
9851
|
+
...input?.label ? { label: input.label } : {},
|
|
9789
9852
|
cause
|
|
9790
9853
|
})));
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
revokedAt: row.value.revokedAt
|
|
9854
|
+
yield* emitUpsert({
|
|
9855
|
+
id,
|
|
9856
|
+
credential,
|
|
9857
|
+
scopes: input?.scopes ?? AuthStandardClientScopes,
|
|
9858
|
+
subject: input?.subject ?? "one-time-token",
|
|
9859
|
+
...input?.label ? { label: input.label } : {},
|
|
9860
|
+
createdAt: now,
|
|
9861
|
+
expiresAt
|
|
9800
9862
|
});
|
|
9801
|
-
return
|
|
9802
|
-
sessionId: row.value.sessionId,
|
|
9803
|
-
token,
|
|
9804
|
-
method: row.value.method,
|
|
9805
|
-
client: toClientMetadata(row.value.client),
|
|
9806
|
-
expiresAt: row.value.expiresAt,
|
|
9807
|
-
subject: row.value.subject,
|
|
9808
|
-
scopes: row.value.scopes
|
|
9809
|
-
};
|
|
9863
|
+
return issued;
|
|
9810
9864
|
});
|
|
9811
|
-
const
|
|
9865
|
+
const consume = Effect.fn("PairingGrantStore.consume")(function* (credential, input) {
|
|
9812
9866
|
const now = yield* DateTime.now;
|
|
9813
|
-
const
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
return next;
|
|
9840
|
-
});
|
|
9841
|
-
yield* emitRemoved(sessionId);
|
|
9842
|
-
}
|
|
9843
|
-
return revoked;
|
|
9844
|
-
});
|
|
9845
|
-
const revokeAllExcept = Effect.fn("SessionStore.revokeAllExcept")(function* (sessionId) {
|
|
9846
|
-
const revokedAt = yield* DateTime.now;
|
|
9847
|
-
const revokedSessionIds = yield* authSessions.revokeAllExcept({
|
|
9848
|
-
currentSessionId: sessionId,
|
|
9849
|
-
revokedAt
|
|
9850
|
-
}).pipe(Effect.mapError((cause) => new OtherSessionsRevocationError({
|
|
9851
|
-
currentSessionId: sessionId,
|
|
9852
|
-
cause
|
|
9853
|
-
})));
|
|
9854
|
-
if (revokedSessionIds.length > 0) {
|
|
9855
|
-
yield* Ref.update(connectedSessionsRef, (current) => {
|
|
9856
|
-
const next = new Map(current);
|
|
9857
|
-
for (const revokedSessionId of revokedSessionIds) next.delete(revokedSessionId);
|
|
9858
|
-
return next;
|
|
9859
|
-
});
|
|
9860
|
-
yield* Effect.forEach(revokedSessionIds, (revokedSessionId) => emitRemoved(revokedSessionId), {
|
|
9861
|
-
concurrency: "unbounded",
|
|
9862
|
-
discard: true
|
|
9867
|
+
const seededResult = yield* Ref.modify(seededGrantsRef, (current) => {
|
|
9868
|
+
const grant = current.get(credential);
|
|
9869
|
+
if (!grant) return [{
|
|
9870
|
+
_tag: "error",
|
|
9871
|
+
reason: "not-found",
|
|
9872
|
+
error: new UnknownBootstrapCredentialError({})
|
|
9873
|
+
}, current];
|
|
9874
|
+
const next = new Map(current);
|
|
9875
|
+
if (DateTime.isGreaterThanOrEqualTo(now, grant.expiresAt)) {
|
|
9876
|
+
next.delete(credential);
|
|
9877
|
+
return [{
|
|
9878
|
+
_tag: "error",
|
|
9879
|
+
reason: "expired",
|
|
9880
|
+
error: new ExpiredBootstrapCredentialError({})
|
|
9881
|
+
}, next];
|
|
9882
|
+
}
|
|
9883
|
+
if (grant.proofKeyThumbprint && grant.proofKeyThumbprint !== input?.proofKeyThumbprint) return [{
|
|
9884
|
+
_tag: "error",
|
|
9885
|
+
reason: "not-found",
|
|
9886
|
+
error: new BootstrapCredentialProofKeyMismatchError({})
|
|
9887
|
+
}, next];
|
|
9888
|
+
const remainingUses = grant.remainingUses;
|
|
9889
|
+
if (typeof remainingUses === "number") if (remainingUses <= 1) next.delete(credential);
|
|
9890
|
+
else next.set(credential, {
|
|
9891
|
+
...grant,
|
|
9892
|
+
remainingUses: remainingUses - 1
|
|
9863
9893
|
});
|
|
9894
|
+
return [{
|
|
9895
|
+
_tag: "success",
|
|
9896
|
+
grant: {
|
|
9897
|
+
method: grant.method,
|
|
9898
|
+
scopes: grant.scopes,
|
|
9899
|
+
subject: grant.subject,
|
|
9900
|
+
...grant.label ? { label: grant.label } : {},
|
|
9901
|
+
...grant.proofKeyThumbprint ? { proofKeyThumbprint: grant.proofKeyThumbprint } : {},
|
|
9902
|
+
expiresAt: grant.expiresAt
|
|
9903
|
+
}
|
|
9904
|
+
}, next];
|
|
9905
|
+
});
|
|
9906
|
+
if (seededResult._tag === "success") return seededResult.grant;
|
|
9907
|
+
if (seededResult.reason !== "not-found") return yield* seededResult.error;
|
|
9908
|
+
const consumed = yield* pairingLinks.consumeAvailable({
|
|
9909
|
+
credential,
|
|
9910
|
+
proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
|
|
9911
|
+
consumedAt: now,
|
|
9912
|
+
now
|
|
9913
|
+
}).pipe(Effect.mapError((cause) => new BootstrapCredentialConsumeAvailableError({ cause })));
|
|
9914
|
+
if (Option.isSome(consumed)) {
|
|
9915
|
+
yield* emitRemoved(consumed.value.id);
|
|
9916
|
+
return {
|
|
9917
|
+
method: consumed.value.method,
|
|
9918
|
+
scopes: consumed.value.scopes,
|
|
9919
|
+
subject: consumed.value.subject,
|
|
9920
|
+
...consumed.value.label ? { label: consumed.value.label } : {},
|
|
9921
|
+
...consumed.value.proofKeyThumbprint ? { proofKeyThumbprint: consumed.value.proofKeyThumbprint } : {},
|
|
9922
|
+
expiresAt: consumed.value.expiresAt
|
|
9923
|
+
};
|
|
9864
9924
|
}
|
|
9865
|
-
|
|
9925
|
+
const matching = yield* pairingLinks.getByCredential({ credential }).pipe(Effect.mapError((cause) => new BootstrapCredentialLookupError({ cause })));
|
|
9926
|
+
if (Option.isNone(matching)) return yield* new UnknownBootstrapCredentialError({});
|
|
9927
|
+
if (matching.value.revokedAt !== null) return yield* new UnavailableBootstrapCredentialError({});
|
|
9928
|
+
if (matching.value.consumedAt !== null) return yield* new UnknownBootstrapCredentialError({});
|
|
9929
|
+
if (DateTime.isGreaterThanOrEqualTo(now, matching.value.expiresAt)) return yield* new ExpiredBootstrapCredentialError({});
|
|
9930
|
+
if (matching.value.proofKeyThumbprint !== null && matching.value.proofKeyThumbprint !== input?.proofKeyThumbprint) return yield* new BootstrapCredentialProofKeyMismatchError({});
|
|
9931
|
+
return yield* new UnavailableBootstrapCredentialError({});
|
|
9866
9932
|
});
|
|
9867
|
-
return
|
|
9868
|
-
|
|
9869
|
-
issue,
|
|
9870
|
-
verify,
|
|
9871
|
-
issueWebSocketToken,
|
|
9872
|
-
verifyWebSocketToken,
|
|
9933
|
+
return PairingGrantStore.of({
|
|
9934
|
+
issueOneTimeToken,
|
|
9873
9935
|
listActive,
|
|
9874
9936
|
get streamChanges() {
|
|
9875
9937
|
return Stream.fromPubSub(changesPubSub);
|
|
9876
9938
|
},
|
|
9877
9939
|
revoke,
|
|
9878
|
-
|
|
9879
|
-
markConnected,
|
|
9880
|
-
markDisconnected
|
|
9940
|
+
consume
|
|
9881
9941
|
});
|
|
9882
9942
|
});
|
|
9883
|
-
const layer$59 = Layer.effect(
|
|
9943
|
+
const layer$59 = Layer.effect(PairingGrantStore, make$63).pipe(Layer.provideMerge(layer$60));
|
|
9884
9944
|
//#endregion
|
|
9885
9945
|
//#region src/persistence/Migrations/001_OrchestrationEvents.ts
|
|
9886
9946
|
var _001_OrchestrationEvents_default = Effect.gen(function* () {
|
|
@@ -11848,8 +11908,8 @@ const make$62 = Effect.gen(function* () {
|
|
|
11848
11908
|
issueStartupPairingUrl
|
|
11849
11909
|
});
|
|
11850
11910
|
});
|
|
11851
|
-
const layer$58 = Layer.effect(EnvironmentAuth, make$62).pipe(Layer.provideMerge(layer$
|
|
11852
|
-
const storageLayer = Layer.mergeAll(layer$
|
|
11911
|
+
const layer$58 = Layer.effect(EnvironmentAuth, make$62).pipe(Layer.provideMerge(layer$59), Layer.provideMerge(layer$61), Layer.provideMerge(layer$64));
|
|
11912
|
+
const storageLayer = Layer.mergeAll(layer$62, layerConfig);
|
|
11853
11913
|
const runtimeLayer = layer$58.pipe(Layer.provideMerge(storageLayer));
|
|
11854
11914
|
//#endregion
|
|
11855
11915
|
//#region src/cliAuthFormat.ts
|
|
@@ -13703,7 +13763,7 @@ const assetNotFound = (name) => HttpServerResponse.jsonUnsafe({
|
|
|
13703
13763
|
*/
|
|
13704
13764
|
const rejectOversizedAsset = Effect.fn("HubHttpServer.rejectOversizedAsset")(function* (input) {
|
|
13705
13765
|
const bytes = agentAssetTotalBytes(input.files);
|
|
13706
|
-
if (bytes >
|
|
13766
|
+
if (bytes > 4194304) return yield* new InvalidHubRequest({ detail: `Asset is ${bytes} bytes, over the ${AGENT_ASSET_TOTAL_MAX_BYTES} byte limit.` });
|
|
13707
13767
|
});
|
|
13708
13768
|
const listAssetsRoute = HttpRouter.add("GET", "/assets", respondToHubFailures(Effect.gen(function* () {
|
|
13709
13769
|
yield* authenticateHubRequest();
|
|
@@ -14389,7 +14449,8 @@ const validateHubLink = (rawBaseUrl, rawToken) => {
|
|
|
14389
14449
|
const StoredHubLink = Schema$1.fromJsonString(Schema$1.Struct({
|
|
14390
14450
|
baseUrl: Schema$1.String,
|
|
14391
14451
|
token: Schema$1.String,
|
|
14392
|
-
syncMode: Schema$1.optional(Schema$1.Literals(["auto", "paused"]))
|
|
14452
|
+
syncMode: Schema$1.optional(Schema$1.Literals(["auto", "paused"])),
|
|
14453
|
+
shareMode: Schema$1.optional(Schema$1.Literals(["all", "selected"]))
|
|
14393
14454
|
}));
|
|
14394
14455
|
const decodeStoredHubLink = Schema$1.decodeUnknownExit(StoredHubLink);
|
|
14395
14456
|
const encodeStoredHubLink = Schema$1.encodeSync(StoredHubLink);
|
|
@@ -14404,11 +14465,13 @@ const parseHubLink = (raw) => {
|
|
|
14404
14465
|
const decoded = decodeStoredHubLink(raw);
|
|
14405
14466
|
if (decoded._tag !== "Success") return {
|
|
14406
14467
|
settings: Option.none(),
|
|
14407
|
-
syncMode: "auto"
|
|
14468
|
+
syncMode: "auto",
|
|
14469
|
+
shareMode: "all"
|
|
14408
14470
|
};
|
|
14409
14471
|
return {
|
|
14410
14472
|
settings: validateHubLink(decoded.value.baseUrl, decoded.value.token),
|
|
14411
|
-
syncMode: decoded.value.syncMode ?? "auto"
|
|
14473
|
+
syncMode: decoded.value.syncMode ?? "auto",
|
|
14474
|
+
shareMode: decoded.value.shareMode ?? "all"
|
|
14412
14475
|
};
|
|
14413
14476
|
};
|
|
14414
14477
|
const formatHubLink = (input) => encodeStoredHubLink({
|
|
@@ -14420,7 +14483,8 @@ const formatHubLink = (input) => encodeStoredHubLink({
|
|
|
14420
14483
|
onNone: () => "",
|
|
14421
14484
|
onSome: (s) => s.token
|
|
14422
14485
|
}),
|
|
14423
|
-
syncMode: input.syncMode
|
|
14486
|
+
syncMode: input.syncMode,
|
|
14487
|
+
shareMode: input.shareMode
|
|
14424
14488
|
});
|
|
14425
14489
|
const fromEnvironment = (environment) => validateHubLink(environment.P4CODE_HUB_URL ?? "", environment.P4CODE_HUB_TOKEN ?? "");
|
|
14426
14490
|
const make$59 = Effect.fn("HubLink.make")(function* (environment) {
|
|
@@ -14432,7 +14496,8 @@ const make$59 = Effect.fn("HubLink.make")(function* (environment) {
|
|
|
14432
14496
|
return Option.match(stored, {
|
|
14433
14497
|
onNone: () => ({
|
|
14434
14498
|
settings: Option.none(),
|
|
14435
|
-
syncMode: "auto"
|
|
14499
|
+
syncMode: "auto",
|
|
14500
|
+
shareMode: "all"
|
|
14436
14501
|
}),
|
|
14437
14502
|
onSome: (bytes) => parseHubLink(new TextDecoder().decode(bytes))
|
|
14438
14503
|
});
|
|
@@ -14440,10 +14505,12 @@ const make$59 = Effect.fn("HubLink.make")(function* (environment) {
|
|
|
14440
14505
|
const toState = (record) => Option.isSome(fromEnv) ? {
|
|
14441
14506
|
settings: fromEnv,
|
|
14442
14507
|
syncMode: record.syncMode,
|
|
14508
|
+
shareMode: record.shareMode,
|
|
14443
14509
|
source: "environment"
|
|
14444
14510
|
} : {
|
|
14445
14511
|
settings: record.settings,
|
|
14446
14512
|
syncMode: record.syncMode,
|
|
14513
|
+
shareMode: record.shareMode,
|
|
14447
14514
|
source: Option.isSome(record.settings) ? "stored" : "none"
|
|
14448
14515
|
};
|
|
14449
14516
|
const cache = yield* Ref.make(yield* readStored());
|
|
@@ -14459,23 +14526,30 @@ const make$59 = Effect.fn("HubLink.make")(function* (environment) {
|
|
|
14459
14526
|
if (Option.isNone(validated)) return yield* new InvalidHubLink({ detail: "A hub needs an http or https URL and a non-empty token." });
|
|
14460
14527
|
const record = yield* Ref.get(cache);
|
|
14461
14528
|
return yield* persist({
|
|
14462
|
-
|
|
14463
|
-
|
|
14529
|
+
...record,
|
|
14530
|
+
settings: validated
|
|
14464
14531
|
});
|
|
14465
14532
|
}),
|
|
14466
14533
|
disconnect: Effect.gen(function* () {
|
|
14467
14534
|
const record = yield* Ref.get(cache);
|
|
14468
14535
|
return yield* persist({
|
|
14469
|
-
|
|
14470
|
-
|
|
14536
|
+
...record,
|
|
14537
|
+
settings: Option.none()
|
|
14471
14538
|
});
|
|
14472
14539
|
}),
|
|
14473
14540
|
setSyncMode: (mode) => Effect.gen(function* () {
|
|
14474
14541
|
const record = yield* Ref.get(cache);
|
|
14475
14542
|
return yield* persist({
|
|
14476
|
-
|
|
14543
|
+
...record,
|
|
14477
14544
|
syncMode: mode
|
|
14478
14545
|
});
|
|
14546
|
+
}),
|
|
14547
|
+
setShareMode: (mode) => Effect.gen(function* () {
|
|
14548
|
+
const record = yield* Ref.get(cache);
|
|
14549
|
+
return yield* persist({
|
|
14550
|
+
...record,
|
|
14551
|
+
shareMode: mode
|
|
14552
|
+
});
|
|
14479
14553
|
})
|
|
14480
14554
|
};
|
|
14481
14555
|
});
|
|
@@ -14776,6 +14850,38 @@ const writeSkillManifest = Effect.fn("skillManifest.write")(function* (manifestP
|
|
|
14776
14850
|
/** Bounded so a stray symlink or a vendored `node_modules` cannot walk forever. */
|
|
14777
14851
|
const MAX_SKILL_DEPTH = 4;
|
|
14778
14852
|
/**
|
|
14853
|
+
* Entries that are never part of a skill, skipped rather than refused.
|
|
14854
|
+
*
|
|
14855
|
+
* The distinction matters. Refusing an asset because it cannot be represented
|
|
14856
|
+
* faithfully is right — a half-materialized skill loads and misbehaves. But a
|
|
14857
|
+
* `.DS_Store` is not part of the skill at all, and macOS drops one into any
|
|
14858
|
+
* directory that has been opened in Finder, so refusing on its account rejects
|
|
14859
|
+
* a perfectly good skill for a reason its author cannot see.
|
|
14860
|
+
*
|
|
14861
|
+
* These could not sync in any case: `AgentAssetFilePath` rejects a leading dot,
|
|
14862
|
+
* so a dotfile has no representable path even if it were text.
|
|
14863
|
+
*/
|
|
14864
|
+
const IGNORED_ENTRIES = /* @__PURE__ */ new Set([
|
|
14865
|
+
".DS_Store",
|
|
14866
|
+
"Thumbs.db",
|
|
14867
|
+
".git",
|
|
14868
|
+
".svn",
|
|
14869
|
+
".hg",
|
|
14870
|
+
"node_modules",
|
|
14871
|
+
"__pycache__",
|
|
14872
|
+
".venv",
|
|
14873
|
+
".pytest_cache",
|
|
14874
|
+
".ruff_cache",
|
|
14875
|
+
".mypy_cache"
|
|
14876
|
+
]);
|
|
14877
|
+
/**
|
|
14878
|
+
* Anything else with a leading dot is skipped too — an editor's `.idea`, a
|
|
14879
|
+
* `.gitignore` that belongs to the checkout rather than the skill. The rule is
|
|
14880
|
+
* the same one the path schema already enforces, applied earlier so it reads as
|
|
14881
|
+
* "not part of the skill" instead of "this skill is broken".
|
|
14882
|
+
*/
|
|
14883
|
+
const isIgnoredEntry = (entry) => IGNORED_ENTRIES.has(entry) || entry.startsWith(".");
|
|
14884
|
+
/**
|
|
14779
14885
|
* A file is binary if decoding it yields a NUL.
|
|
14780
14886
|
*
|
|
14781
14887
|
* Cheap, and right for what is being asked: the question is not "is this valid
|
|
@@ -14791,6 +14897,7 @@ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (roo
|
|
|
14791
14897
|
if (depth > MAX_SKILL_DEPTH) return "too-deep";
|
|
14792
14898
|
const entries = yield* fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed(() => []));
|
|
14793
14899
|
for (const entry of [...entries].sort()) {
|
|
14900
|
+
if (isIgnoredEntry(entry)) continue;
|
|
14794
14901
|
const absolute = path.join(directory, entry);
|
|
14795
14902
|
const relative = prefix === "" ? entry : `${prefix}/${entry}`;
|
|
14796
14903
|
const info = yield* fileSystem.stat(absolute).pipe(Effect.orElseSucceed(() => void 0));
|
|
@@ -14801,7 +14908,7 @@ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (roo
|
|
|
14801
14908
|
continue;
|
|
14802
14909
|
}
|
|
14803
14910
|
if (info.type !== "File") continue;
|
|
14804
|
-
if (files.length >=
|
|
14911
|
+
if (files.length >= 512) return "too-many-files";
|
|
14805
14912
|
const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
|
|
14806
14913
|
if (content === void 0) return "unreadable";
|
|
14807
14914
|
if (looksBinary(content)) return "binary-file";
|
|
@@ -14815,7 +14922,7 @@ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (roo
|
|
|
14815
14922
|
});
|
|
14816
14923
|
const refusal = yield* walk(root, "", 1);
|
|
14817
14924
|
if (refusal !== null) return { refusal };
|
|
14818
|
-
if (agentAssetTotalBytes(files) >
|
|
14925
|
+
if (agentAssetTotalBytes(files) > 4194304) return { refusal: "asset-too-large" };
|
|
14819
14926
|
return { files };
|
|
14820
14927
|
});
|
|
14821
14928
|
/**
|
|
@@ -14832,6 +14939,7 @@ const readSkillDirectory = Effect.fn("skillDirectory.readSkillDirectory")(functi
|
|
|
14832
14939
|
const skills = [];
|
|
14833
14940
|
const refused = [];
|
|
14834
14941
|
for (const entry of [...entries].sort()) {
|
|
14942
|
+
if (isIgnoredEntry(entry)) continue;
|
|
14835
14943
|
const directory = path.join(skillsRoot, entry);
|
|
14836
14944
|
if ((yield* fileSystem.stat(directory).pipe(Effect.orElseSucceed(() => void 0)))?.type !== "Directory") continue;
|
|
14837
14945
|
if (!(yield* fileSystem.exists(path.join(directory, "SKILL.md")).pipe(Effect.orElseSucceed(() => false)))) continue;
|
|
@@ -15146,14 +15254,41 @@ const make$57 = Effect.gen(function* () {
|
|
|
15146
15254
|
break;
|
|
15147
15255
|
}
|
|
15148
15256
|
yield* writeSkillManifest(manifestPath, withEntriesFor(manifest, skillsRoot, [...entries.values()]));
|
|
15257
|
+
const shareMode = (yield* link.current).shareMode;
|
|
15258
|
+
const autoShared = [];
|
|
15259
|
+
if (shareMode === "all") {
|
|
15260
|
+
for (const name of plan.unmanaged) {
|
|
15261
|
+
const skill = local.skills.find((entry) => entry.name === name);
|
|
15262
|
+
if (skill === void 0) continue;
|
|
15263
|
+
const written = yield* client.write({
|
|
15264
|
+
kind: "skill",
|
|
15265
|
+
name,
|
|
15266
|
+
files: skill.files
|
|
15267
|
+
}).pipe(Effect.option);
|
|
15268
|
+
if (Option.isNone(written)) {
|
|
15269
|
+
conflicts.push({
|
|
15270
|
+
name,
|
|
15271
|
+
reason: "unmanaged-local-collision"
|
|
15272
|
+
});
|
|
15273
|
+
continue;
|
|
15274
|
+
}
|
|
15275
|
+
entries.set(name, {
|
|
15276
|
+
name,
|
|
15277
|
+
version: written.value.version,
|
|
15278
|
+
digest: digestSkillFiles(written.value.files)
|
|
15279
|
+
});
|
|
15280
|
+
autoShared.push(name);
|
|
15281
|
+
}
|
|
15282
|
+
yield* writeSkillManifest(manifestPath, withEntriesFor(manifest, skillsRoot, [...entries.values()]));
|
|
15283
|
+
}
|
|
15149
15284
|
return {
|
|
15150
15285
|
ran: true,
|
|
15151
15286
|
skillsRoot,
|
|
15152
15287
|
materialized,
|
|
15153
|
-
published,
|
|
15288
|
+
published: [...published, ...autoShared],
|
|
15154
15289
|
removed,
|
|
15155
15290
|
conflicts,
|
|
15156
|
-
unmanaged: plan.unmanaged,
|
|
15291
|
+
unmanaged: plan.unmanaged.filter((name) => !autoShared.includes(name)),
|
|
15157
15292
|
refused: local.refused,
|
|
15158
15293
|
unavailable: null
|
|
15159
15294
|
};
|
|
@@ -15165,13 +15300,14 @@ const make$57 = Effect.gen(function* () {
|
|
|
15165
15300
|
* sharing the executing code path is one `if` away from writing during a
|
|
15166
15301
|
* preview, and this is the call a panel makes every time it opens.
|
|
15167
15302
|
*/
|
|
15168
|
-
const
|
|
15303
|
+
const skillStatus = Effect.gen(function* () {
|
|
15169
15304
|
const state = yield* link.current;
|
|
15170
15305
|
const skillsRoot = yield* resolveSkillsRoot();
|
|
15171
15306
|
const local = yield* readSkillDirectory(skillsRoot);
|
|
15172
15307
|
const managed = entriesFor(yield* readSkillManifest(manifestPath), skillsRoot);
|
|
15173
15308
|
const base = {
|
|
15174
15309
|
syncMode: state.syncMode,
|
|
15310
|
+
shareMode: state.shareMode,
|
|
15175
15311
|
source: state.source,
|
|
15176
15312
|
skillsRoot,
|
|
15177
15313
|
refused: local.refused
|
|
@@ -15256,6 +15392,7 @@ const make$57 = Effect.gen(function* () {
|
|
|
15256
15392
|
configured: false,
|
|
15257
15393
|
hubUrl: null,
|
|
15258
15394
|
syncMode: "auto",
|
|
15395
|
+
shareMode: "all",
|
|
15259
15396
|
source: "none",
|
|
15260
15397
|
skillsRoot: null,
|
|
15261
15398
|
entries: [],
|
|
@@ -15313,6 +15450,27 @@ const make$57 = Effect.gen(function* () {
|
|
|
15313
15450
|
published: [name]
|
|
15314
15451
|
};
|
|
15315
15452
|
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("skill publish failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
|
|
15453
|
+
const publishAll = Effect.gen(function* () {
|
|
15454
|
+
if (!(yield* isConfigured)) return EMPTY_REPORT;
|
|
15455
|
+
const skillsRoot = yield* resolveSkillsRoot();
|
|
15456
|
+
const status = yield* skillStatus;
|
|
15457
|
+
const candidates = status.entries.filter((entry) => entry.state === "unmanaged").map((entry) => entry.name);
|
|
15458
|
+
const published = [];
|
|
15459
|
+
const conflicts = [];
|
|
15460
|
+
for (const name of candidates) {
|
|
15461
|
+
const report = yield* publish(name);
|
|
15462
|
+
published.push(...report.published);
|
|
15463
|
+
conflicts.push(...report.conflicts);
|
|
15464
|
+
}
|
|
15465
|
+
return {
|
|
15466
|
+
...EMPTY_REPORT,
|
|
15467
|
+
ran: true,
|
|
15468
|
+
skillsRoot,
|
|
15469
|
+
published,
|
|
15470
|
+
conflicts,
|
|
15471
|
+
refused: status.refused
|
|
15472
|
+
};
|
|
15473
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("skill publish-all failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
|
|
15316
15474
|
const unpublish = (name) => Effect.gen(function* () {
|
|
15317
15475
|
if (!(yield* isConfigured)) return EMPTY_REPORT;
|
|
15318
15476
|
const skillsRoot = yield* resolveSkillsRoot();
|
|
@@ -15348,15 +15506,16 @@ const make$57 = Effect.gen(function* () {
|
|
|
15348
15506
|
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("skill unpublish failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
|
|
15349
15507
|
return {
|
|
15350
15508
|
run,
|
|
15351
|
-
status,
|
|
15509
|
+
status: skillStatus,
|
|
15352
15510
|
publish,
|
|
15511
|
+
publishAll,
|
|
15353
15512
|
unpublish
|
|
15354
15513
|
};
|
|
15355
15514
|
});
|
|
15356
15515
|
const layer$52 = Layer.effect(SkillSync, make$57);
|
|
15357
15516
|
//#endregion
|
|
15358
15517
|
//#region src/cli/skills.ts
|
|
15359
|
-
const SyncServicesLive = layer$52.pipe(Layer.provideMerge(layer$53), Layer.provideMerge(layer$54), Layer.provideMerge(layer$55.pipe(Layer.provide(layer$
|
|
15518
|
+
const SyncServicesLive = layer$52.pipe(Layer.provideMerge(layer$53), Layer.provideMerge(layer$54), Layer.provideMerge(layer$55.pipe(Layer.provide(layer$62))), Layer.provideMerge(layer$62), Layer.provideMerge(FetchHttpClient.layer));
|
|
15360
15519
|
const runWithSync = (flags, run) => Effect.gen(function* () {
|
|
15361
15520
|
const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
|
|
15362
15521
|
return yield* Effect.gen(function* () {
|
|
@@ -15384,6 +15543,7 @@ const formatStatus = (status) => {
|
|
|
15384
15543
|
else {
|
|
15385
15544
|
lines.push(`Hub: ${status.hubUrl ?? "unknown"}${status.source === "environment" ? " (from the environment)" : ""}`);
|
|
15386
15545
|
lines.push(`Sync: ${status.syncMode}`);
|
|
15546
|
+
lines.push(`Share: ${status.shareMode === "all" ? "all skills (new ones share automatically)" : "only the ones you choose"}`);
|
|
15387
15547
|
}
|
|
15388
15548
|
if (status.skillsRoot !== null) lines.push(`Skills: ${status.skillsRoot}`);
|
|
15389
15549
|
if (status.unavailable !== null) lines.push(`Warning: ${status.unavailable}`);
|
|
@@ -15406,7 +15566,10 @@ const formatReport = (report) => {
|
|
|
15406
15566
|
if (report.removed.length > 0) parts.push(`removed ${report.removed.join(", ")}`);
|
|
15407
15567
|
for (const conflict of report.conflicts) parts.push(`CONFLICT on ${conflict.name} (${conflict.reason})`);
|
|
15408
15568
|
for (const refusal of report.refused) parts.push(`cannot sync ${refusal.name} (${refusal.reason})`);
|
|
15409
|
-
|
|
15569
|
+
const summary = parts.length === 0 ? "Already up to date." : parts.join("\n");
|
|
15570
|
+
if (report.unmanaged.length === 0) return summary;
|
|
15571
|
+
const count = report.unmanaged.length;
|
|
15572
|
+
return `${summary}\n\n${count} local skill${count === 1 ? " is" : "s are"} not shared. Share ${count === 1 ? "it" : "them"} with: p4 skills publish --all`;
|
|
15410
15573
|
};
|
|
15411
15574
|
const urlFlag = Flag.string("url").pipe(Flag.withDescription("Base URL of the hub, for example https://hub.example.com."));
|
|
15412
15575
|
const tokenFlag = Flag.string("token").pipe(Flag.withDescription("Bearer token for this server, from `p4 hub token add`."));
|
|
@@ -15428,14 +15591,29 @@ const hubDisconnectCommand = Command.make("disconnect", { ...authLocationFlags }
|
|
|
15428
15591
|
}))));
|
|
15429
15592
|
const statusCommand = Command.make("status", { ...authLocationFlags }).pipe(Command.withDescription("Show which skills are shared, changed, or in conflict."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.status.pipe(Effect.flatMap((s) => Console.log(formatStatus(s)))))));
|
|
15430
15593
|
const syncCommand = Command.make("sync", { ...authLocationFlags }).pipe(Command.withDescription("Reconcile this machine's shared skills with the hub now."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.run.pipe(Effect.flatMap((r) => Console.log(formatReport(r)))))));
|
|
15594
|
+
const allFlag = Flag.boolean("all").pipe(Flag.withDescription("Share every local skill this machine does not already share."));
|
|
15431
15595
|
const publishCommand = Command.make("publish", {
|
|
15432
15596
|
...authLocationFlags,
|
|
15433
|
-
name: skillNameArgument
|
|
15434
|
-
|
|
15597
|
+
name: skillNameArgument.pipe(Argument.optional),
|
|
15598
|
+
all: allFlag
|
|
15599
|
+
}).pipe(Command.withDescription("Share local skills through the hub, so every machine gets them."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => Effect.gen(function* () {
|
|
15600
|
+
if (flags.all) return yield* sync.publishAll.pipe(Effect.flatMap((report) => Console.log(formatReport(report))));
|
|
15601
|
+
const name = Option.getOrUndefined(flags.name);
|
|
15602
|
+
if (name === void 0) return yield* Console.error("Name a skill, or pass --all to share every unshared one.");
|
|
15603
|
+
return yield* sync.publish(name).pipe(Effect.flatMap((report) => Console.log(formatReport(report))));
|
|
15604
|
+
}))));
|
|
15435
15605
|
const unpublishCommand = Command.make("unpublish", {
|
|
15436
15606
|
...authLocationFlags,
|
|
15437
15607
|
name: skillNameArgument
|
|
15438
15608
|
}).pipe(Command.withDescription("Stop sharing a skill. It stays on this machine and leaves the others."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.unpublish(flags.name).pipe(Effect.flatMap((report) => Console.log(formatReport(report)))))));
|
|
15609
|
+
const shareAllCommand = Command.make("share-all", { ...authLocationFlags }).pipe(Command.withDescription("Share every skill, including new ones, automatically. The default."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
|
|
15610
|
+
yield* link.setShareMode("all");
|
|
15611
|
+
yield* Console.log(formatStatus(yield* sync.status));
|
|
15612
|
+
}))));
|
|
15613
|
+
const shareSelectedCommand = Command.make("share-selected", { ...authLocationFlags }).pipe(Command.withDescription("Share only the skills you name with `publish`. Nothing shares on its own."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
|
|
15614
|
+
yield* link.setShareMode("selected");
|
|
15615
|
+
yield* Console.log(formatStatus(yield* sync.status));
|
|
15616
|
+
}))));
|
|
15439
15617
|
const pauseCommand = Command.make("pause", { ...authLocationFlags }).pipe(Command.withDescription("Stop syncing automatically. `p4 skills sync` still works."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
|
|
15440
15618
|
yield* link.setSyncMode("paused");
|
|
15441
15619
|
yield* Console.log(formatStatus(yield* sync.status));
|
|
@@ -15450,7 +15628,9 @@ const skillsCommand = Command.make("skills", {}).pipe(Command.withDescription("S
|
|
|
15450
15628
|
publishCommand,
|
|
15451
15629
|
unpublishCommand,
|
|
15452
15630
|
pauseCommand,
|
|
15453
|
-
resumeCommand
|
|
15631
|
+
resumeCommand,
|
|
15632
|
+
shareAllCommand,
|
|
15633
|
+
shareSelectedCommand
|
|
15454
15634
|
]));
|
|
15455
15635
|
/** Exported for the hub command, which owns `connect`/`disconnect`. */
|
|
15456
15636
|
const hubLinkCommands = [hubConnectCommand, hubDisconnectCommand];
|
|
@@ -39446,9 +39626,11 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
|
|
|
39446
39626
|
[WS_METHODS.hubConnect, AuthOrchestrationOperateScope],
|
|
39447
39627
|
[WS_METHODS.hubDisconnect, AuthOrchestrationOperateScope],
|
|
39448
39628
|
[WS_METHODS.hubSetSyncMode, AuthOrchestrationOperateScope],
|
|
39629
|
+
[WS_METHODS.hubSetShareMode, AuthOrchestrationOperateScope],
|
|
39449
39630
|
[WS_METHODS.hubMintToken, AuthOrchestrationOperateScope],
|
|
39450
39631
|
[WS_METHODS.skillsSync, AuthOrchestrationOperateScope],
|
|
39451
39632
|
[WS_METHODS.skillsPublish, AuthOrchestrationOperateScope],
|
|
39633
|
+
[WS_METHODS.skillsPublishAll, AuthOrchestrationOperateScope],
|
|
39452
39634
|
[WS_METHODS.skillsUnpublish, AuthOrchestrationOperateScope],
|
|
39453
39635
|
[WS_METHODS.serverGetSettings, AuthOrchestrationReadScope],
|
|
39454
39636
|
[WS_METHODS.serverUpdateSettings, AuthOrchestrationOperateScope],
|
|
@@ -40026,6 +40208,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
40026
40208
|
[WS_METHODS.hubConnect]: (input) => observeRpcEffect$1(WS_METHODS.hubConnect, hubLink.connect(input).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(skillSync.status)), { "rpc.aggregate": "hub" }),
|
|
40027
40209
|
[WS_METHODS.hubDisconnect]: (_input) => observeRpcEffect$1(WS_METHODS.hubDisconnect, hubLink.disconnect.pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(skillSync.status)), { "rpc.aggregate": "hub" }),
|
|
40028
40210
|
[WS_METHODS.hubSetSyncMode]: ({ mode }) => observeRpcEffect$1(WS_METHODS.hubSetSyncMode, hubLink.setSyncMode(mode).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(skillSync.status)), { "rpc.aggregate": "hub" }),
|
|
40211
|
+
[WS_METHODS.hubSetShareMode]: ({ mode }) => observeRpcEffect$1(WS_METHODS.hubSetShareMode, hubLink.setShareMode(mode).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(skillSync.status)), { "rpc.aggregate": "hub" }),
|
|
40029
40212
|
[WS_METHODS.hubMintToken]: (_input) => observeRpcEffect$1(WS_METHODS.hubMintToken, Effect.gen(function* () {
|
|
40030
40213
|
const token = yield* crypto.randomBytes(HUB_TOKEN_BYTES).pipe(Effect.map((bytes) => Buffer.from(bytes).toString("base64url")), Effect.orDie);
|
|
40031
40214
|
return {
|
|
@@ -40045,6 +40228,12 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
40045
40228
|
status: yield* skillSync.status
|
|
40046
40229
|
};
|
|
40047
40230
|
}), { "rpc.aggregate": "skills" }),
|
|
40231
|
+
[WS_METHODS.skillsPublishAll]: (_input) => observeRpcEffect$1(WS_METHODS.skillsPublishAll, Effect.gen(function* () {
|
|
40232
|
+
return {
|
|
40233
|
+
report: yield* skillSync.publishAll,
|
|
40234
|
+
status: yield* skillSync.status
|
|
40235
|
+
};
|
|
40236
|
+
}), { "rpc.aggregate": "skills" }),
|
|
40048
40237
|
[WS_METHODS.skillsUnpublish]: ({ name }) => observeRpcEffect$1(WS_METHODS.skillsUnpublish, Effect.gen(function* () {
|
|
40049
40238
|
return {
|
|
40050
40239
|
report: yield* skillSync.unpublish(name),
|
|
@@ -81336,9 +81525,9 @@ const WorkspaceEntriesLayerLive = layer$26.pipe(Layer.provide(layer$42));
|
|
|
81336
81525
|
const WorkspaceFileSystemLayerLive = layer$25.pipe(Layer.provide(layer$42), Layer.provide(WorkspaceEntriesLayerLive));
|
|
81337
81526
|
const WorkspaceLayerLive = Layer.mergeAll(layer$42, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
|
|
81338
81527
|
const ProjectFaviconResolverLayerLive = layer$40.pipe(Layer.provide(layer$42), Layer.provide(layer$41));
|
|
81339
|
-
const AuthLayerLive = layer$58.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$
|
|
81528
|
+
const AuthLayerLive = layer$58.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$62));
|
|
81340
81529
|
const ProviderRuntimeLayerLive = ProviderSessionReaperLive.pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
|
|
81341
|
-
const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(CheckpointingLayerLive), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), Layer.provideMerge(PersistenceLayerLive), Layer.provideMerge(layer$50), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$55.pipe(Layer.provide(layer$
|
|
81530
|
+
const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(CheckpointingLayerLive), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), Layer.provideMerge(PersistenceLayerLive), Layer.provideMerge(layer$50), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$55.pipe(Layer.provide(layer$62))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$51), Layer.provideMerge(layer$44), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$62)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$47), Layer.provideMerge(layer$49), Layer.provideMerge(layer$48), Layer.provide(layer$66));
|
|
81342
81531
|
/**
|
|
81343
81532
|
* Hub asset sync.
|
|
81344
81533
|
*
|