@dreamboard-games/cli 0.1.30-alpha.12 → 0.1.30-alpha.15
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 +2 -6
- package/dist/agent-verifier/agent-workspace-verifier.mjs +18 -17
- package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -1
- package/dist/agent-verifier/{chunk-VLOIZDR6.mjs → chunk-4BECKTAF.mjs} +199 -190
- package/dist/agent-verifier/chunk-4BECKTAF.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-TLYGTHXU.mjs → chunk-5GCZZ6NW.mjs} +3 -3
- package/dist/agent-verifier/{chunk-YR664DJX.mjs → chunk-A67WUYN2.mjs} +42 -68
- package/dist/agent-verifier/chunk-A67WUYN2.mjs.map +1 -0
- package/dist/agent-verifier/chunk-AXXUGU7Q.mjs +255 -0
- package/dist/agent-verifier/chunk-AXXUGU7Q.mjs.map +1 -0
- package/dist/agent-verifier/chunk-CO3BRUD6.mjs +342 -0
- package/dist/agent-verifier/chunk-CO3BRUD6.mjs.map +1 -0
- package/dist/agent-verifier/chunk-DPYC2NDB.mjs +59 -0
- package/dist/agent-verifier/chunk-DPYC2NDB.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-4GU3PCHV.mjs → chunk-DWLTCUUX.mjs} +576 -393
- package/dist/agent-verifier/chunk-DWLTCUUX.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-COB56ESI.mjs → chunk-G2ECODRB.mjs} +2 -2
- package/dist/agent-verifier/{chunk-6XRC5PWB.mjs → chunk-H3XNWKJU.mjs} +217 -232
- package/dist/agent-verifier/chunk-H3XNWKJU.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-Z6OZWUIZ.mjs → chunk-HLHT57AW.mjs} +64 -16
- package/dist/agent-verifier/chunk-HLHT57AW.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-YDIOW2BO.mjs → chunk-INIK6LHK.mjs} +2 -2
- package/dist/agent-verifier/{chunk-UWJIZML3.mjs → chunk-LKQ557TJ.mjs} +30 -23
- package/dist/agent-verifier/chunk-LKQ557TJ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-NAK77WXW.mjs → chunk-MYMVXTZT.mjs} +4 -5
- package/dist/agent-verifier/chunk-MYMVXTZT.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-UIJ2NDG6.mjs → chunk-NFL3Z4Z7.mjs} +31 -238
- package/dist/agent-verifier/chunk-NFL3Z4Z7.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-XKCJBIRY.mjs → chunk-QD4SQNUP.mjs} +2 -2
- package/dist/agent-verifier/{chunk-IAYRNVUC.mjs → chunk-RDYXWXXC.mjs} +1 -3
- package/dist/agent-verifier/{chunk-QBAF7EYR.mjs → chunk-TTB7AIHZ.mjs} +4 -4
- package/dist/agent-verifier/{chunk-QBAF7EYR.mjs.map → chunk-TTB7AIHZ.mjs.map} +1 -1
- package/dist/agent-verifier/chunk-V6AQDR7W.mjs +89 -0
- package/dist/agent-verifier/chunk-V6AQDR7W.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-RHI6S4SU.mjs → chunk-V7ABTZXW.mjs} +1 -3
- package/dist/agent-verifier/{chunk-RHI6S4SU.mjs.map → chunk-V7ABTZXW.mjs.map} +1 -1
- package/dist/agent-verifier/chunk-WAFBU5U7.mjs +467 -0
- package/dist/agent-verifier/chunk-WAFBU5U7.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-3IJBOLGT.mjs → chunk-WSIYUUSD.mjs} +2 -2
- package/dist/agent-verifier/{compile-WZ7X6I2A.mjs → compile-6G6GENLP.mjs} +22 -18
- package/dist/agent-verifier/compile-6G6GENLP.mjs.map +1 -0
- package/dist/agent-verifier/{global-config-XHL7BCKN.mjs → global-config-6UGFPLDA.mjs} +4 -3
- package/dist/agent-verifier/{keychain-backend-A3MRWLPF.mjs → keychain-backend-BQLW5VEC.mjs} +11 -6
- package/dist/agent-verifier/keychain-backend-BQLW5VEC.mjs.map +1 -0
- package/dist/agent-verifier/{local-files-ZW52HSVT.mjs → local-files-WPHUV6GU.mjs} +6 -6
- package/dist/agent-verifier/{materialize-workspace-BKZLLFI4.mjs → materialize-workspace-S24JA46C.mjs} +17 -17
- package/dist/agent-verifier/materialize-workspace-S24JA46C.mjs.map +1 -0
- package/dist/agent-verifier/{reducer-bundle-preflight-7NYZF5ZT.mjs → reducer-bundle-preflight-3DSXIELT.mjs} +4 -4
- package/dist/agent-verifier/reducer-contract-preflight-FQB7M4PU.mjs +11 -0
- package/dist/agent-verifier/{reducer-native-test-harness-D4VWPIAC.mjs → reducer-native-test-harness-GY2CCQWN.mjs} +12 -9
- package/dist/agent-verifier/{static-scaffold-JCRBDKEH.mjs → static-scaffold-5YD6QHIS.mjs} +7 -9
- package/dist/agent-verifier/{sync-ELLJEWMB.mjs → sync-3OZBFABA.mjs} +24 -22
- package/dist/agent-verifier/{sync-ELLJEWMB.mjs.map → sync-3OZBFABA.mjs.map} +1 -1
- package/dist/agent-verifier/{test-OSXBPLSP.mjs → test-LQAGEQLY.mjs} +19 -17
- package/dist/agent-verifier/test-LQAGEQLY.mjs.map +1 -0
- package/dist/agent-verifier/{workspace-codegen-WPZHMATU.mjs → workspace-codegen-4IWICKLB.mjs} +3 -3
- package/dist/agent-verifier/{workspace-dependencies-ULZZZPNX.mjs → workspace-dependencies-ZMHPHVQV.mjs} +2 -2
- package/dist/authoring-compatibility-internal.js +12 -0
- package/dist/{agent-verifier/chunk-W2MDP5ZN.mjs → chunk-AVOAT522.js} +118 -21
- package/dist/chunk-AVOAT522.js.map +1 -0
- package/dist/chunk-FFO2IJL3.js +204 -0
- package/dist/chunk-FFO2IJL3.js.map +1 -0
- package/dist/chunk-JMV5ZFMK.js +4298 -0
- package/dist/chunk-JMV5ZFMK.js.map +1 -0
- package/dist/{chunk-P5TITCD3.js → chunk-JSF7PMIF.js} +2240 -4554
- package/dist/chunk-JSF7PMIF.js.map +1 -0
- package/dist/{global-config-WPJRXVDO.js → global-config-NLGAFSRU.js} +3 -2
- package/dist/global-config-NLGAFSRU.js.map +1 -0
- package/dist/index.js +1371 -3545
- package/dist/index.js.map +1 -1
- package/dist/internal.js +14 -8
- package/dist/{keychain-backend-JHTXAKWC.js → keychain-backend-47LZ5IX5.js} +11 -6
- package/dist/keychain-backend-47LZ5IX5.js.map +1 -0
- package/package.json +9 -19
- package/release/authoring-release-set.json +38 -0
- package/skills/dreamboard/references/manifest-authoring.md +11 -3
- package/dist/agent-verifier/chunk-4GU3PCHV.mjs.map +0 -1
- package/dist/agent-verifier/chunk-6XRC5PWB.mjs.map +0 -1
- package/dist/agent-verifier/chunk-G42BGGG2.mjs +0 -70
- package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +0 -1
- package/dist/agent-verifier/chunk-KK47X7RV.mjs +0 -14
- package/dist/agent-verifier/chunk-KK47X7RV.mjs.map +0 -1
- package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +0 -1
- package/dist/agent-verifier/chunk-UIJ2NDG6.mjs.map +0 -1
- package/dist/agent-verifier/chunk-UWJIZML3.mjs.map +0 -1
- package/dist/agent-verifier/chunk-VLOIZDR6.mjs.map +0 -1
- package/dist/agent-verifier/chunk-W2MDP5ZN.mjs.map +0 -1
- package/dist/agent-verifier/chunk-YR664DJX.mjs.map +0 -1
- package/dist/agent-verifier/chunk-Z6OZWUIZ.mjs.map +0 -1
- package/dist/agent-verifier/compile-WZ7X6I2A.mjs.map +0 -1
- package/dist/agent-verifier/keychain-backend-A3MRWLPF.mjs.map +0 -1
- package/dist/agent-verifier/materialize-workspace-BKZLLFI4.mjs.map +0 -1
- package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs +0 -11
- package/dist/agent-verifier/test-OSXBPLSP.mjs.map +0 -1
- package/dist/chunk-GXM7RRZJ.js +0 -433
- package/dist/chunk-GXM7RRZJ.js.map +0 -1
- package/dist/chunk-P5TITCD3.js.map +0 -1
- package/dist/dev-host/components/drawer.tsx +0 -132
- package/dist/dev-host/components/input.tsx +0 -21
- package/dist/dev-host/dev-api-proxy-plugin.ts +0 -328
- package/dist/dev-host/dev-author-dom-warnings.ts +0 -100
- package/dist/dev-host/dev-diagnostics.ts +0 -62
- package/dist/dev-host/dev-fallback-stylesheet.ts +0 -53
- package/dist/dev-host/dev-hmr-guard-plugin.ts +0 -47
- package/dist/dev-host/dev-host-controller.ts +0 -674
- package/dist/dev-host/dev-host-player-query.ts +0 -17
- package/dist/dev-host/dev-host-session-transport.ts +0 -52
- package/dist/dev-host/dev-host-storage.ts +0 -56
- package/dist/dev-host/dev-log-relay-plugin.ts +0 -510
- package/dist/dev-host/dev-runtime-config.ts +0 -14
- package/dist/dev-host/dev-runtime-platform.ts +0 -335
- package/dist/dev-host/dev-virtual-modules-plugin.ts +0 -64
- package/dist/dev-host/host-main.css +0 -224
- package/dist/dev-host/host-main.tsx +0 -954
- package/dist/dev-host/index.html +0 -56
- package/dist/dev-host/lib/utils.ts +0 -6
- package/dist/dev-host/plugin-main.ts +0 -61
- package/dist/dev-host/plugin.html +0 -24
- package/dist/dev-host/shared-styles.css +0 -144
- package/dist/dev-host/start-dev-server.ts +0 -140
- package/dist/dev-host/virtual-modules.d.ts +0 -27
- package/dist/keychain-backend-JHTXAKWC.js.map +0 -1
- /package/dist/agent-verifier/{chunk-TLYGTHXU.mjs.map → chunk-5GCZZ6NW.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-COB56ESI.mjs.map → chunk-G2ECODRB.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-YDIOW2BO.mjs.map → chunk-INIK6LHK.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-XKCJBIRY.mjs.map → chunk-QD4SQNUP.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-IAYRNVUC.mjs.map → chunk-RDYXWXXC.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-3IJBOLGT.mjs.map → chunk-WSIYUUSD.mjs.map} +0 -0
- /package/dist/agent-verifier/{global-config-XHL7BCKN.mjs.map → global-config-6UGFPLDA.mjs.map} +0 -0
- /package/dist/agent-verifier/{local-files-ZW52HSVT.mjs.map → local-files-WPHUV6GU.mjs.map} +0 -0
- /package/dist/agent-verifier/{reducer-bundle-preflight-7NYZF5ZT.mjs.map → reducer-bundle-preflight-3DSXIELT.mjs.map} +0 -0
- /package/dist/agent-verifier/{reducer-contract-preflight-COD2CO22.mjs.map → reducer-contract-preflight-FQB7M4PU.mjs.map} +0 -0
- /package/dist/agent-verifier/{reducer-native-test-harness-D4VWPIAC.mjs.map → reducer-native-test-harness-GY2CCQWN.mjs.map} +0 -0
- /package/dist/agent-verifier/{static-scaffold-JCRBDKEH.mjs.map → static-scaffold-5YD6QHIS.mjs.map} +0 -0
- /package/dist/agent-verifier/{workspace-codegen-WPZHMATU.mjs.map → workspace-codegen-4IWICKLB.mjs.map} +0 -0
- /package/dist/agent-verifier/{workspace-dependencies-ULZZZPNX.mjs.map → workspace-dependencies-ZMHPHVQV.mjs.map} +0 -0
- /package/dist/{global-config-WPJRXVDO.js.map → authoring-compatibility-internal.js.map} +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.framework.json +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.json +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/index.tsx +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/style.css +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.framework.json +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.json +0 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
atomicWriteFile,
|
|
4
|
+
withFileLock
|
|
5
|
+
} from "./chunk-TAEQKBJB.mjs";
|
|
6
|
+
import {
|
|
7
|
+
PROJECT_DIR_NAME
|
|
8
|
+
} from "./chunk-M7UVBANQ.mjs";
|
|
9
|
+
|
|
10
|
+
// src/config/credential-store.ts
|
|
11
|
+
import os from "os";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { promises as fs } from "fs";
|
|
14
|
+
|
|
15
|
+
// src/build-target.ts
|
|
16
|
+
var injectedBuildChannel = true ? "development" : void 0;
|
|
17
|
+
var BUILD_CHANNEL = injectedBuildChannel === "published" ? "published" : "development";
|
|
18
|
+
var IS_PUBLISHED_BUILD = BUILD_CHANNEL === "published";
|
|
19
|
+
var PUBLISHED_ENVIRONMENT = "prod";
|
|
20
|
+
|
|
21
|
+
// src/config/credential-store.ts
|
|
22
|
+
function getCredentialFilePath() {
|
|
23
|
+
return path.join(os.homedir(), PROJECT_DIR_NAME, "auth.json");
|
|
24
|
+
}
|
|
25
|
+
function getCredentialLockPath() {
|
|
26
|
+
return `${getCredentialFilePath()}.lock`;
|
|
27
|
+
}
|
|
28
|
+
async function fileRead() {
|
|
29
|
+
const filePath = getCredentialFilePath();
|
|
30
|
+
let data;
|
|
31
|
+
try {
|
|
32
|
+
data = await fs.readFile(filePath, "utf8");
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err.code === "ENOENT") return null;
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
if (data.trim().length === 0) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(data);
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const accessToken = parsed.clerkAccessToken ?? parsed.accessToken ?? parsed.authToken;
|
|
47
|
+
const refreshToken = parsed.refreshToken;
|
|
48
|
+
if (!accessToken && !refreshToken) return null;
|
|
49
|
+
return {
|
|
50
|
+
accessToken: accessToken || void 0,
|
|
51
|
+
refreshToken: refreshToken || void 0,
|
|
52
|
+
tokenExpiresAt: parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || void 0,
|
|
53
|
+
dreamboardApiToken: parsed.dreamboardApiToken || void 0,
|
|
54
|
+
dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || void 0,
|
|
55
|
+
clerkOAuthIssuer: parsed.clerkOAuthIssuer || void 0,
|
|
56
|
+
clerkOAuthClientId: parsed.clerkOAuthClientId || void 0,
|
|
57
|
+
clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || void 0,
|
|
58
|
+
environment: parsed.environment || void 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function writeFilePayload(payload) {
|
|
62
|
+
await atomicWriteFile(
|
|
63
|
+
getCredentialFilePath(),
|
|
64
|
+
`${JSON.stringify(payload, null, 2)}
|
|
65
|
+
`,
|
|
66
|
+
{ mode: 384 }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
async function fileWriteFull(creds) {
|
|
70
|
+
if (!creds.accessToken || !creds.refreshToken) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"Refusing to persist credentials with an empty accessToken or refreshToken."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
await writeFilePayload({
|
|
76
|
+
clerkAccessToken: creds.accessToken,
|
|
77
|
+
refreshToken: creds.refreshToken,
|
|
78
|
+
clerkAccessExpiresAt: creds.tokenExpiresAt,
|
|
79
|
+
dreamboardApiToken: creds.dreamboardApiToken,
|
|
80
|
+
dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,
|
|
81
|
+
clerkOAuthIssuer: creds.clerkOAuthIssuer,
|
|
82
|
+
clerkOAuthClientId: creds.clerkOAuthClientId,
|
|
83
|
+
clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,
|
|
84
|
+
environment: creds.environment
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async function fileWriteAccessOnly(accessToken) {
|
|
88
|
+
if (!accessToken) {
|
|
89
|
+
throw new Error("Refusing to persist an empty access token.");
|
|
90
|
+
}
|
|
91
|
+
await writeFilePayload({ authToken: accessToken });
|
|
92
|
+
}
|
|
93
|
+
async function fileClear() {
|
|
94
|
+
const filePath = getCredentialFilePath();
|
|
95
|
+
try {
|
|
96
|
+
await fs.unlink(filePath);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
if (err.code !== "ENOENT") throw err;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
var fileCredentialBackend = {
|
|
102
|
+
name: "file",
|
|
103
|
+
read: fileRead,
|
|
104
|
+
writeFull: fileWriteFull,
|
|
105
|
+
writeAccessOnly: fileWriteAccessOnly,
|
|
106
|
+
clear: fileClear
|
|
107
|
+
};
|
|
108
|
+
var CredentialStoreUnavailableError = class extends Error {
|
|
109
|
+
code = "CREDENTIAL_STORE_UNAVAILABLE";
|
|
110
|
+
constructor(reason) {
|
|
111
|
+
super(`Credential store unavailable: ${reason}`);
|
|
112
|
+
this.name = "CredentialStoreUnavailableError";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var cachedBackend = null;
|
|
116
|
+
var migrationCompleted = false;
|
|
117
|
+
var backendResolver = defaultBackendResolver;
|
|
118
|
+
async function defaultBackendResolver() {
|
|
119
|
+
const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? "").trim().toLowerCase();
|
|
120
|
+
if (IS_PUBLISHED_BUILD) {
|
|
121
|
+
if (override && override !== "keychain" && override !== "auto") {
|
|
122
|
+
throw new CredentialStoreUnavailableError(
|
|
123
|
+
"published builds require the OS credential store"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const { tryKeychainBackend: tryKeychainBackend2 } = await import("./keychain-backend-BQLW5VEC.mjs");
|
|
127
|
+
const keychain2 = await tryKeychainBackend2();
|
|
128
|
+
if (keychain2.available) {
|
|
129
|
+
return keychain2.backend;
|
|
130
|
+
}
|
|
131
|
+
throw new CredentialStoreUnavailableError(keychain2.reason);
|
|
132
|
+
}
|
|
133
|
+
if (override === "file") {
|
|
134
|
+
return fileCredentialBackend;
|
|
135
|
+
}
|
|
136
|
+
if (override && override !== "keychain" && override !== "auto") {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Unknown DREAMBOARD_CREDENTIAL_BACKEND value "${override}" (expected "file", "keychain", or "auto").`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const useKeychain = override === "keychain" || await readCredentialBackendPreference();
|
|
142
|
+
if (!useKeychain) {
|
|
143
|
+
return fileCredentialBackend;
|
|
144
|
+
}
|
|
145
|
+
const { tryKeychainBackend } = await import("./keychain-backend-BQLW5VEC.mjs");
|
|
146
|
+
const keychain = await tryKeychainBackend();
|
|
147
|
+
if (keychain.available) {
|
|
148
|
+
return keychain.backend;
|
|
149
|
+
}
|
|
150
|
+
return fileCredentialBackend;
|
|
151
|
+
}
|
|
152
|
+
async function readCredentialBackendPreference() {
|
|
153
|
+
try {
|
|
154
|
+
const { loadGlobalConfig } = await import("./global-config-6UGFPLDA.mjs");
|
|
155
|
+
const config = await loadGlobalConfig();
|
|
156
|
+
return config.credentialBackend === "keychain";
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function getCredentialBackend() {
|
|
162
|
+
if (cachedBackend === null) {
|
|
163
|
+
cachedBackend = await backendResolver();
|
|
164
|
+
if (!migrationCompleted && cachedBackend.name !== "file") {
|
|
165
|
+
await migrateFromFileBackendIfNeeded(cachedBackend, {
|
|
166
|
+
failClosed: IS_PUBLISHED_BUILD
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
migrationCompleted = true;
|
|
170
|
+
}
|
|
171
|
+
return cachedBackend;
|
|
172
|
+
}
|
|
173
|
+
async function migrateFromFileBackendIfNeeded(target, options = {}) {
|
|
174
|
+
try {
|
|
175
|
+
const [onDisk, onTarget] = await Promise.all([
|
|
176
|
+
fileCredentialBackend.read(),
|
|
177
|
+
target.read()
|
|
178
|
+
]);
|
|
179
|
+
if (!onDisk) return;
|
|
180
|
+
if (onTarget) {
|
|
181
|
+
await fileCredentialBackend.clear();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (onDisk.accessToken && onDisk.refreshToken) {
|
|
185
|
+
const migrated = {
|
|
186
|
+
accessToken: onDisk.accessToken,
|
|
187
|
+
refreshToken: onDisk.refreshToken,
|
|
188
|
+
tokenExpiresAt: onDisk.tokenExpiresAt,
|
|
189
|
+
dreamboardApiToken: onDisk.dreamboardApiToken,
|
|
190
|
+
dreamboardApiExpiresAt: onDisk.dreamboardApiExpiresAt,
|
|
191
|
+
clerkOAuthIssuer: onDisk.clerkOAuthIssuer,
|
|
192
|
+
clerkOAuthClientId: onDisk.clerkOAuthClientId,
|
|
193
|
+
clerkOAuthTokenUrl: onDisk.clerkOAuthTokenUrl,
|
|
194
|
+
environment: onDisk.environment
|
|
195
|
+
};
|
|
196
|
+
await target.writeFull(migrated);
|
|
197
|
+
await verifyMigratedSession(target, migrated);
|
|
198
|
+
} else if (onDisk.accessToken) {
|
|
199
|
+
await target.writeAccessOnly(onDisk.accessToken);
|
|
200
|
+
const migrated = await target.read();
|
|
201
|
+
if (migrated?.accessToken !== onDisk.accessToken) {
|
|
202
|
+
throw new Error("Credential migration verification failed.");
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
await fileCredentialBackend.clear();
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (options.failClosed) {
|
|
210
|
+
throw new CredentialStoreUnavailableError(
|
|
211
|
+
error instanceof Error ? error.message : String(error)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function verifyMigratedSession(target, expected) {
|
|
217
|
+
const migrated = await target.read();
|
|
218
|
+
if (migrated?.accessToken !== expected.accessToken || migrated.refreshToken !== expected.refreshToken) {
|
|
219
|
+
throw new Error("Credential migration verification failed.");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function getStoredSession() {
|
|
223
|
+
if (process.env.DREAMBOARD_AGENT_TOKEN?.trim()) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const backend = await getCredentialBackend();
|
|
227
|
+
return backend.read();
|
|
228
|
+
}
|
|
229
|
+
async function withCredentialLock(fn, options) {
|
|
230
|
+
return withFileLock(
|
|
231
|
+
getCredentialLockPath(),
|
|
232
|
+
async () => {
|
|
233
|
+
const backend = await getCredentialBackend();
|
|
234
|
+
const ops = {
|
|
235
|
+
backendName: backend.name,
|
|
236
|
+
read: () => backend.read(),
|
|
237
|
+
writeFull: (creds) => backend.writeFull(creds),
|
|
238
|
+
writeAccessOnly: (accessToken) => backend.writeAccessOnly(accessToken),
|
|
239
|
+
clear: () => backend.clear()
|
|
240
|
+
};
|
|
241
|
+
return fn(ops);
|
|
242
|
+
},
|
|
243
|
+
options
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export {
|
|
248
|
+
BUILD_CHANNEL,
|
|
249
|
+
IS_PUBLISHED_BUILD,
|
|
250
|
+
PUBLISHED_ENVIRONMENT,
|
|
251
|
+
getCredentialFilePath,
|
|
252
|
+
getStoredSession,
|
|
253
|
+
withCredentialLock
|
|
254
|
+
};
|
|
255
|
+
//# sourceMappingURL=chunk-AXXUGU7Q.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/config/credential-store.ts","../../src/build-target.ts"],"sourcesContent":["/**\n * Single writer for the long-lived Dreamboard session credentials.\n *\n * Design invariants (enforced at the type level and tested in\n * `credential-store.test.ts`):\n *\n * 1. This module is the ONLY place in the CLI that writes credentials to\n * disk or the OS keychain. `global-config.ts` used to own both the\n * config and the credentials via `saveGlobalConfig`, which made it\n * trivial to wipe a refresh token by accident. The `GlobalConfig` type\n * no longer carries credentials, so attempting to persist one through\n * the config path is a type error.\n *\n * 2. The mutating surface is intentionally narrow:\n * - `setCredentials(c)` for refreshable sessions (both tokens present)\n * - `setAccessOnlySession(accessToken)` for the `auth set` / `config set\n * --token` power-user path, which has no refresh token by\n * construction\n * - `clearCredentials()` wipes the file entirely\n * There is no \"partial update\" API. `Credentials` requires both\n * `accessToken` and `refreshToken`, so it is impossible to persist a\n * half-populated refreshable session.\n *\n * 3. Writes go through `atomicWriteFile` + `withFileLock`, so a crash or\n * interrupt during `dreamboard sync`/`compile` cannot leave `auth.json`\n * truncated, and parallel CLI invocations cannot clobber each other's\n * rotated refresh tokens.\n *\n * 4. The on-disk JSON shape for the file backend is kept backward\n * compatible: we continue to read/write `authToken` + `refreshToken`\n * so existing users are not forced to log in again after this change.\n * A newer `accessToken` key is also accepted for read to ease any\n * future format bump.\n *\n * 5. Development builds may still use the file backend for local testing.\n * Published builds require the OS keychain and fail closed when it is\n * unavailable.\n */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { promises as fs } from \"node:fs\";\nimport { PROJECT_DIR_NAME } from \"../constants.js\";\nimport { IS_PUBLISHED_BUILD } from \"../build-target.js\";\nimport {\n atomicWriteFile,\n withFileLock,\n type FileLockOptions,\n} from \"../utils/atomic-file.js\";\n\n/**\n * Fully refreshable session. `accessToken` is the Clerk OAuth bootstrap token\n * retained for refresh/exchange compatibility; ordinary API calls use\n * `dreamboardApiToken`.\n */\nexport type Credentials = {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly tokenExpiresAt?: string;\n readonly dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\n/**\n * Raw on-disk snapshot. Either or both fields may be present. The refresh\n * coordinator only acts on snapshots that have both tokens populated.\n */\nexport type StoredSessionSnapshot = {\n readonly accessToken?: string;\n readonly refreshToken?: string;\n readonly tokenExpiresAt?: string;\n readonly dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\nexport type CredentialBackendName = \"file\" | \"keychain\";\n\nexport type CredentialBackend = {\n readonly name: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(): Promise<void>;\n};\n\nexport type CredentialLockOps = {\n readonly backendName: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(): Promise<void>;\n};\n\ntype DiskShape = Partial<{\n clerkAccessToken: string;\n clerkAccessExpiresAt: string;\n accessToken: string;\n authToken: string;\n refreshToken: string;\n tokenExpiresAt: string;\n dreamboardApiToken: string;\n dreamboardApiExpiresAt: string;\n clerkOAuthIssuer: string;\n clerkOAuthClientId: string;\n clerkOAuthTokenUrl: string;\n environment: string;\n}>;\n\nexport function getCredentialFilePath(): string {\n return path.join(os.homedir(), PROJECT_DIR_NAME, \"auth.json\");\n}\n\nfunction getCredentialLockPath(): string {\n return `${getCredentialFilePath()}.lock`;\n}\n\nasync function fileRead(): Promise<StoredSessionSnapshot | null> {\n const filePath = getCredentialFilePath();\n let data: string;\n try {\n data = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n if (data.trim().length === 0) {\n return null;\n }\n let parsed: DiskShape;\n try {\n parsed = JSON.parse(data) as DiskShape;\n } catch {\n return null;\n }\n const accessToken =\n parsed.clerkAccessToken ?? parsed.accessToken ?? parsed.authToken;\n const refreshToken = parsed.refreshToken;\n if (!accessToken && !refreshToken) return null;\n return {\n accessToken: accessToken || undefined,\n refreshToken: refreshToken || undefined,\n tokenExpiresAt:\n parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || undefined,\n dreamboardApiToken: parsed.dreamboardApiToken || undefined,\n dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || undefined,\n clerkOAuthIssuer: parsed.clerkOAuthIssuer || undefined,\n clerkOAuthClientId: parsed.clerkOAuthClientId || undefined,\n clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || undefined,\n environment: parsed.environment || undefined,\n };\n}\n\nasync function writeFilePayload(payload: DiskShape): Promise<void> {\n await atomicWriteFile(\n getCredentialFilePath(),\n `${JSON.stringify(payload, null, 2)}\\n`,\n { mode: 0o600 },\n );\n}\n\nasync function fileWriteFull(creds: Credentials): Promise<void> {\n if (!creds.accessToken || !creds.refreshToken) {\n throw new Error(\n \"Refusing to persist credentials with an empty accessToken or refreshToken.\",\n );\n }\n await writeFilePayload({\n clerkAccessToken: creds.accessToken,\n refreshToken: creds.refreshToken,\n clerkAccessExpiresAt: creds.tokenExpiresAt,\n dreamboardApiToken: creds.dreamboardApiToken,\n dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,\n clerkOAuthIssuer: creds.clerkOAuthIssuer,\n clerkOAuthClientId: creds.clerkOAuthClientId,\n clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,\n environment: creds.environment,\n });\n}\n\nasync function fileWriteAccessOnly(accessToken: string): Promise<void> {\n if (!accessToken) {\n throw new Error(\"Refusing to persist an empty access token.\");\n }\n await writeFilePayload({ authToken: accessToken });\n}\n\nasync function fileClear(): Promise<void> {\n const filePath = getCredentialFilePath();\n try {\n await fs.unlink(filePath);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\nexport const fileCredentialBackend: CredentialBackend = {\n name: \"file\",\n read: fileRead,\n writeFull: fileWriteFull,\n writeAccessOnly: fileWriteAccessOnly,\n clear: fileClear,\n};\n\nexport type BackendResolver = () =>\n | CredentialBackend\n | Promise<CredentialBackend>;\n\nexport class CredentialStoreUnavailableError extends Error {\n readonly code = \"CREDENTIAL_STORE_UNAVAILABLE\";\n\n constructor(reason: string) {\n super(`Credential store unavailable: ${reason}`);\n this.name = \"CredentialStoreUnavailableError\";\n }\n}\n\nlet cachedBackend: CredentialBackend | null = null;\nlet migrationCompleted = false;\nlet backendResolver: BackendResolver = defaultBackendResolver;\n\n/**\n * Development resolver precedence:\n *\n * 1. `DREAMBOARD_CREDENTIAL_BACKEND` env var (debugging / CI override).\n * - \"file\" -> force file\n * - \"keychain\" -> force keychain (falls back to file if the native\n * module or the OS keyring is unavailable)\n * - \"auto\" -> same as unset (use config)\n * - unknown -> throw so typos fail loud\n * 2. `credentialBackend` in `~/.dreamboard/config.json`.\n * - \"keychain\" -> opt in to the OS keychain (with file fallback)\n * - \"file\" / unset / malformed -> file\n * 3. Default: file backend.\n *\n * Published builds skip this precedence, require keychain, and throw\n * CREDENTIAL_STORE_UNAVAILABLE instead of falling back to plaintext.\n *\n * In development, keychain is opt-in because on macOS the OS login-keychain prompts for\n * the user's password the first time a new binary tries to write to an\n * item, and re-prompts whenever the Node binary signature changes. We\n * would rather ship a zero-prompt default and let users who care about\n * encrypted-at-rest storage enable it.\n *\n * The resolver is async because the keychain probe requires a dynamic\n * `@napi-rs/keyring` import.\n */\nasync function defaultBackendResolver(): Promise<CredentialBackend> {\n const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? \"\")\n .trim()\n .toLowerCase();\n if (IS_PUBLISHED_BUILD) {\n if (override && override !== \"keychain\" && override !== \"auto\") {\n throw new CredentialStoreUnavailableError(\n \"published builds require the OS credential store\",\n );\n }\n const { tryKeychainBackend } = await import(\"./keychain-backend.js\");\n const keychain = await tryKeychainBackend();\n if (keychain.available) {\n return keychain.backend;\n }\n throw new CredentialStoreUnavailableError(keychain.reason);\n }\n\n if (override === \"file\") {\n return fileCredentialBackend;\n }\n if (override && override !== \"keychain\" && override !== \"auto\") {\n // Fail loud on typos rather than silently falling back: this env\n // var exists specifically for users who are debugging auth issues\n // and need to know their override took effect.\n throw new Error(\n `Unknown DREAMBOARD_CREDENTIAL_BACKEND value \"${override}\" (expected \"file\", \"keychain\", or \"auto\").`,\n );\n }\n\n const useKeychain =\n override === \"keychain\" || (await readCredentialBackendPreference());\n if (!useKeychain) {\n return fileCredentialBackend;\n }\n\n const { tryKeychainBackend } = await import(\"./keychain-backend.js\");\n const keychain = await tryKeychainBackend();\n if (keychain.available) {\n return keychain.backend;\n }\n // The user explicitly asked for keychain but the platform can't\n // provide one (no libsecret on Linux, missing native module, etc).\n // Silently degrade to the file backend so the CLI stays usable; the\n // active backend is still visible through `dreamboard auth status`.\n return fileCredentialBackend;\n}\n\nasync function readCredentialBackendPreference(): Promise<boolean> {\n try {\n // Dynamic import to avoid a top-level cycle with `global-config.ts`\n // (which imports `getCredentialFilePath` from this module). Using\n // the async path keeps the cycle purely lazy.\n const { loadGlobalConfig } = await import(\"./global-config.js\");\n const config = await loadGlobalConfig();\n return config.credentialBackend === \"keychain\";\n } catch {\n // If the config file is unreadable or the dynamic import fails\n // (e.g. during early bootstrap), fall back to the file-backed\n // default rather than crashing credential lookups.\n return false;\n }\n}\n\n/**\n * Override which backend is used. Tests use this to inject in-memory\n * backends; production code uses the default keychain-first resolver.\n */\nexport function setCredentialBackendResolver(resolver: BackendResolver): void {\n backendResolver = resolver;\n cachedBackend = null;\n migrationCompleted = false;\n}\n\nexport async function getCredentialBackend(): Promise<CredentialBackend> {\n if (cachedBackend === null) {\n cachedBackend = await backendResolver();\n // One-time migration: if we resolved to a non-file backend and\n // `auth.json` still has credentials from the old layout, copy them\n // over and remove the file. We only do this when the new backend is\n // empty, so repeated migrations cannot stomp a newer keychain\n // session with a stale file session.\n if (!migrationCompleted && cachedBackend.name !== \"file\") {\n await migrateFromFileBackendIfNeeded(cachedBackend, {\n failClosed: IS_PUBLISHED_BUILD,\n });\n }\n migrationCompleted = true;\n }\n return cachedBackend;\n}\n\nasync function migrateFromFileBackendIfNeeded(\n target: CredentialBackend,\n options: { failClosed?: boolean } = {},\n): Promise<void> {\n try {\n const [onDisk, onTarget] = await Promise.all([\n fileCredentialBackend.read(),\n target.read(),\n ]);\n if (!onDisk) return;\n if (onTarget) {\n // Target already has a session - the user has already migrated.\n // Remove the file so it cannot get re-used accidentally.\n await fileCredentialBackend.clear();\n return;\n }\n if (onDisk.accessToken && onDisk.refreshToken) {\n const migrated: Credentials = {\n accessToken: onDisk.accessToken,\n refreshToken: onDisk.refreshToken,\n tokenExpiresAt: onDisk.tokenExpiresAt,\n dreamboardApiToken: onDisk.dreamboardApiToken,\n dreamboardApiExpiresAt: onDisk.dreamboardApiExpiresAt,\n clerkOAuthIssuer: onDisk.clerkOAuthIssuer,\n clerkOAuthClientId: onDisk.clerkOAuthClientId,\n clerkOAuthTokenUrl: onDisk.clerkOAuthTokenUrl,\n environment: onDisk.environment,\n };\n await target.writeFull(migrated);\n await verifyMigratedSession(target, migrated);\n } else if (onDisk.accessToken) {\n await target.writeAccessOnly(onDisk.accessToken);\n const migrated = await target.read();\n if (migrated?.accessToken !== onDisk.accessToken) {\n throw new Error(\"Credential migration verification failed.\");\n }\n } else {\n return;\n }\n await fileCredentialBackend.clear();\n } catch (error) {\n if (options.failClosed) {\n throw new CredentialStoreUnavailableError(\n error instanceof Error ? error.message : String(error),\n );\n }\n // Migration is best-effort. A failure here should not block CLI\n // operation; on next run the file backend is still consulted\n // directly because the keychain backend's `read` returns null and\n // callers fall through to \"missing session\" → login prompt.\n }\n}\n\nasync function verifyMigratedSession(\n target: CredentialBackend,\n expected: Credentials,\n): Promise<void> {\n const migrated = await target.read();\n if (\n migrated?.accessToken !== expected.accessToken ||\n migrated.refreshToken !== expected.refreshToken\n ) {\n throw new Error(\"Credential migration verification failed.\");\n }\n}\n\nexport async function getActiveCredentialBackendName(): Promise<CredentialBackendName> {\n const backend = await getCredentialBackend();\n return backend.name;\n}\n\n/** Loose read: returns whatever is on disk, including access-only sessions. */\nexport async function getStoredSession(): Promise<StoredSessionSnapshot | null> {\n if (process.env.DREAMBOARD_AGENT_TOKEN?.trim()) {\n return null;\n }\n const backend = await getCredentialBackend();\n return backend.read();\n}\n\n/** Strict read: returns a refreshable pair, or null if either token is missing. */\nexport async function getCredentials(): Promise<Credentials | null> {\n const snapshot = await getStoredSession();\n if (!snapshot) return null;\n const { accessToken, refreshToken } = snapshot;\n if (!accessToken || !refreshToken) return null;\n return {\n accessToken,\n refreshToken,\n tokenExpiresAt: snapshot.tokenExpiresAt,\n dreamboardApiToken: snapshot.dreamboardApiToken,\n dreamboardApiExpiresAt: snapshot.dreamboardApiExpiresAt,\n clerkOAuthIssuer: snapshot.clerkOAuthIssuer,\n clerkOAuthClientId: snapshot.clerkOAuthClientId,\n clerkOAuthTokenUrl: snapshot.clerkOAuthTokenUrl,\n environment: snapshot.environment,\n };\n}\n\nexport async function setCredentials(creds: Credentials): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.writeFull(creds);\n });\n}\n\nexport async function setAccessOnlySession(accessToken: string): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.writeAccessOnly(accessToken);\n });\n}\n\nexport async function clearCredentials(): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.clear();\n });\n}\n\n/**\n * Run `fn` while holding the cross-process credential lock. `fn` receives\n * an ops handle that reads/writes the active backend without re-acquiring\n * the lock (avoiding deadlock).\n *\n * This is the only correct way to perform a read-modify-write on stored\n * credentials (e.g. CLI refresh rotation) in the presence of\n * concurrent CLI invocations.\n */\nexport async function withCredentialLock<T>(\n fn: (ops: CredentialLockOps) => Promise<T>,\n options?: FileLockOptions,\n): Promise<T> {\n return withFileLock(\n getCredentialLockPath(),\n async () => {\n const backend = await getCredentialBackend();\n const ops: CredentialLockOps = {\n backendName: backend.name,\n read: () => backend.read(),\n writeFull: (creds) => backend.writeFull(creds),\n writeAccessOnly: (accessToken) => backend.writeAccessOnly(accessToken),\n clear: () => backend.clear(),\n };\n return fn(ops);\n },\n options,\n );\n}\n\n/** Test-only reset of module state. Not exported through the barrel. */\nexport function _resetCredentialStoreForTests(): void {\n cachedBackend = null;\n migrationCompleted = false;\n backendResolver = defaultBackendResolver;\n}\n","declare const __DREAMBOARD_BUILD_CHANNEL__: string | undefined;\n\nconst injectedBuildChannel =\n typeof __DREAMBOARD_BUILD_CHANNEL__ === \"string\"\n ? __DREAMBOARD_BUILD_CHANNEL__\n : undefined;\n\nexport const BUILD_CHANNEL =\n injectedBuildChannel === \"published\" ? \"published\" : \"development\";\n\nexport const IS_PUBLISHED_BUILD = BUILD_CHANNEL === \"published\";\nexport const PUBLISHED_ENVIRONMENT = \"prod\" as const;\n"],"mappings":"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","names":["tryKeychainBackend","keychain"]}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
IS_PUBLISHED_BUILD,
|
|
4
|
+
withCredentialLock
|
|
5
|
+
} from "./chunk-AXXUGU7Q.mjs";
|
|
6
|
+
|
|
7
|
+
// src/auth/clerk-oauth.ts
|
|
8
|
+
import crypto from "crypto";
|
|
9
|
+
async function refreshClerkOAuthToken(input) {
|
|
10
|
+
const { clientId, tokenUrl } = assertConfigured(input.config);
|
|
11
|
+
const body = new URLSearchParams({
|
|
12
|
+
grant_type: "refresh_token",
|
|
13
|
+
client_id: clientId,
|
|
14
|
+
refresh_token: input.refreshToken
|
|
15
|
+
});
|
|
16
|
+
return requestClerkToken(tokenUrl, body);
|
|
17
|
+
}
|
|
18
|
+
function assertConfigured(config) {
|
|
19
|
+
const issuer = config.issuer?.trim().replace(/\/$/, "");
|
|
20
|
+
const clientId = config.clientId?.trim();
|
|
21
|
+
if (!issuer || !clientId) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
[
|
|
24
|
+
"Clerk OAuth CLI is not configured for this environment.",
|
|
25
|
+
"The CLI expects first-party environments to be configured in its built-in registry.",
|
|
26
|
+
"If this environment has no registered public Clerk OAuth client, create one and release a CLI with its client id.",
|
|
27
|
+
"For emergency overrides, set the environment-specific DREAMBOARD_<ENV>_CLERK_OAUTH_* variables or DREAMBOARD_CLERK_OAUTH_*.",
|
|
28
|
+
"For local harness auth, use `pnpm auth:local` or the auto-bootstrapped local harness flows instead."
|
|
29
|
+
].join(" ")
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
issuer,
|
|
34
|
+
clientId,
|
|
35
|
+
tokenUrl: config.tokenUrl?.trim() || new URL("/oauth/token", issuer).toString(),
|
|
36
|
+
scope: config.scope?.trim() || void 0
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async function requestClerkToken(tokenUrl, body) {
|
|
40
|
+
const response = await fetch(tokenUrl, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
44
|
+
Accept: "application/json"
|
|
45
|
+
},
|
|
46
|
+
body
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const detail = await response.text();
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Clerk OAuth token request failed (${response.status}): ${detail}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const payload = await response.json();
|
|
55
|
+
if (typeof payload.access_token !== "string") {
|
|
56
|
+
throw new Error("Clerk OAuth token response did not include access_token.");
|
|
57
|
+
}
|
|
58
|
+
if (typeof payload.refresh_token !== "string") {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"Clerk OAuth token response did not include refresh_token."
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const expiresAt = typeof payload.expires_in === "number" ? new Date(Date.now() + payload.expires_in * 1e3).toISOString() : void 0;
|
|
64
|
+
return {
|
|
65
|
+
accessToken: payload.access_token,
|
|
66
|
+
refreshToken: payload.refresh_token,
|
|
67
|
+
expiresAt,
|
|
68
|
+
tokenUrl
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/auth/token-exchange.ts
|
|
73
|
+
async function exchangeDreamboardUserToken(input) {
|
|
74
|
+
const fetchImpl = input.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
75
|
+
const response = await fetchImpl(
|
|
76
|
+
new URL("/api/auth/token-exchange", input.apiBaseUrl),
|
|
77
|
+
{
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: {
|
|
80
|
+
Authorization: `Bearer ${input.clerkAccessToken}`,
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
Accept: "application/json"
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify({ audience: input.audience })
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Dreamboard token exchange failed (${response.status}). Run \`dreamboard login\` to authenticate again.`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const payload = await response.json();
|
|
93
|
+
if (typeof payload.accessToken !== "string" || payload.accessToken === "") {
|
|
94
|
+
throw new Error("Dreamboard token exchange response omitted accessToken.");
|
|
95
|
+
}
|
|
96
|
+
if (payload.tokenType !== "Bearer") {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"Dreamboard token exchange response had invalid tokenType."
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (payload.audience !== input.audience) {
|
|
102
|
+
throw new Error("Dreamboard token exchange response had wrong audience.");
|
|
103
|
+
}
|
|
104
|
+
const expiresIn = typeof payload.expiresIn === "number" && Number.isFinite(payload.expiresIn) ? payload.expiresIn : void 0;
|
|
105
|
+
return {
|
|
106
|
+
accessToken: payload.accessToken,
|
|
107
|
+
tokenType: "Bearer",
|
|
108
|
+
audience: input.audience,
|
|
109
|
+
expiresIn,
|
|
110
|
+
expiresAt: expiresIn === void 0 ? void 0 : new Date(Date.now() + expiresIn * 1e3).toISOString()
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/auth/user-token-manager.ts
|
|
115
|
+
var TOKEN_REFRESH_WINDOW_MS = 60 * 1e3;
|
|
116
|
+
function createUserTokenManager(config) {
|
|
117
|
+
return {
|
|
118
|
+
async resolveApiToken() {
|
|
119
|
+
const localOrInjected = resolveNonStoredToken(config, "dreamboard-api");
|
|
120
|
+
if (localOrInjected) return localOrInjected;
|
|
121
|
+
if (!usesStoredSession(config)) return null;
|
|
122
|
+
return withCredentialLock(async (ops) => {
|
|
123
|
+
const stored = await ops.read();
|
|
124
|
+
const apiToken = freshStoredApiToken(stored);
|
|
125
|
+
if (apiToken) return apiToken;
|
|
126
|
+
const clerk = await resolveFreshClerkAccessToken(config, stored);
|
|
127
|
+
const exchanged = await exchangeDreamboardUserToken({
|
|
128
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
129
|
+
clerkAccessToken: clerk.accessToken,
|
|
130
|
+
audience: "dreamboard-api"
|
|
131
|
+
});
|
|
132
|
+
await ops.writeFull({
|
|
133
|
+
...clerk,
|
|
134
|
+
dreamboardApiToken: exchanged.accessToken,
|
|
135
|
+
dreamboardApiExpiresAt: exchanged.expiresAt
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
token: exchanged.accessToken,
|
|
139
|
+
expiresAt: exchanged.expiresAt,
|
|
140
|
+
audience: "dreamboard-api"
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
async resolveGitToken() {
|
|
145
|
+
const localOrInjected = resolveNonStoredToken(config, "dreamboard-git");
|
|
146
|
+
if (localOrInjected) return localOrInjected;
|
|
147
|
+
if (!usesStoredSession(config)) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
"Missing Dreamboard session. Run `dreamboard login` to authenticate."
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return withCredentialLock(async (ops) => {
|
|
153
|
+
const stored = await ops.read();
|
|
154
|
+
const clerk = await resolveFreshClerkAccessToken(config, stored);
|
|
155
|
+
const exchanged = await exchangeDreamboardUserToken({
|
|
156
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
157
|
+
clerkAccessToken: clerk.accessToken,
|
|
158
|
+
audience: "dreamboard-git"
|
|
159
|
+
});
|
|
160
|
+
await ops.writeFull(clerk);
|
|
161
|
+
return {
|
|
162
|
+
token: exchanged.accessToken,
|
|
163
|
+
expiresAt: exchanged.expiresAt,
|
|
164
|
+
audience: "dreamboard-git"
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function resolveNonStoredToken(config, audience) {
|
|
171
|
+
if (usesStoredSession(config)) return null;
|
|
172
|
+
if (!config.authToken) return null;
|
|
173
|
+
return {
|
|
174
|
+
token: config.authToken,
|
|
175
|
+
expiresAt: config.tokenExpiresAt,
|
|
176
|
+
audience
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function freshStoredApiToken(stored) {
|
|
180
|
+
if (!stored?.dreamboardApiToken) return null;
|
|
181
|
+
if (isFresh(stored.dreamboardApiExpiresAt, stored.dreamboardApiToken)) {
|
|
182
|
+
return {
|
|
183
|
+
token: stored.dreamboardApiToken,
|
|
184
|
+
expiresAt: stored.dreamboardApiExpiresAt,
|
|
185
|
+
audience: "dreamboard-api"
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
async function resolveFreshClerkAccessToken(config, stored) {
|
|
191
|
+
const accessToken = stored?.accessToken ?? config.clerkAccessToken;
|
|
192
|
+
const refreshToken = stored?.refreshToken ?? config.refreshToken;
|
|
193
|
+
const tokenExpiresAt = stored?.tokenExpiresAt ?? config.clerkAccessExpiresAt;
|
|
194
|
+
if (!refreshToken) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
"Stored Dreamboard session is missing its refresh token. Run `dreamboard login` to authenticate again."
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (accessToken && isFresh(tokenExpiresAt, accessToken)) {
|
|
200
|
+
return {
|
|
201
|
+
accessToken,
|
|
202
|
+
refreshToken,
|
|
203
|
+
tokenExpiresAt,
|
|
204
|
+
dreamboardApiToken: stored?.dreamboardApiToken,
|
|
205
|
+
dreamboardApiExpiresAt: stored?.dreamboardApiExpiresAt,
|
|
206
|
+
clerkOAuthIssuer: stored?.clerkOAuthIssuer ?? config.clerkOAuthIssuer,
|
|
207
|
+
clerkOAuthClientId: stored?.clerkOAuthClientId ?? config.clerkOAuthClientId,
|
|
208
|
+
clerkOAuthTokenUrl: stored?.clerkOAuthTokenUrl ?? config.clerkOAuthTokenUrl,
|
|
209
|
+
environment: stored?.environment ?? config.environment
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const payload = await refreshClerkOAuthToken({
|
|
213
|
+
config: {
|
|
214
|
+
issuer: stored?.clerkOAuthIssuer ?? config.clerkOAuthIssuer,
|
|
215
|
+
clientId: stored?.clerkOAuthClientId ?? config.clerkOAuthClientId,
|
|
216
|
+
tokenUrl: stored?.clerkOAuthTokenUrl ?? config.clerkOAuthTokenUrl
|
|
217
|
+
},
|
|
218
|
+
refreshToken
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
accessToken: payload.accessToken,
|
|
222
|
+
refreshToken: payload.refreshToken,
|
|
223
|
+
tokenExpiresAt: payload.expiresAt,
|
|
224
|
+
clerkOAuthIssuer: stored?.clerkOAuthIssuer ?? config.clerkOAuthIssuer,
|
|
225
|
+
clerkOAuthClientId: stored?.clerkOAuthClientId ?? config.clerkOAuthClientId,
|
|
226
|
+
clerkOAuthTokenUrl: payload.tokenUrl,
|
|
227
|
+
environment: stored?.environment ?? config.environment
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function isFresh(expiresAt, token) {
|
|
231
|
+
const expiry = expiresAt ? new Date(expiresAt) : getJwtExpiry(token);
|
|
232
|
+
return expiry !== null && Number.isFinite(expiry.getTime()) && expiry.getTime() > Date.now() + TOKEN_REFRESH_WINDOW_MS;
|
|
233
|
+
}
|
|
234
|
+
function getJwtExpiry(accessToken) {
|
|
235
|
+
if (!accessToken) return null;
|
|
236
|
+
const parts = accessToken.split(".");
|
|
237
|
+
if (parts.length !== 3) return null;
|
|
238
|
+
try {
|
|
239
|
+
const payload = JSON.parse(
|
|
240
|
+
Buffer.from(parts[1], "base64url").toString("utf8")
|
|
241
|
+
);
|
|
242
|
+
if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
return new Date(payload.exp * 1e3);
|
|
246
|
+
} catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function usesStoredSession(config) {
|
|
251
|
+
return config.refreshTokenSource === "global";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/config/local-harness-auth.ts
|
|
255
|
+
import { createHmac, randomUUID } from "crypto";
|
|
256
|
+
var DEFAULT_SUBJECT = "harness-smoke-local@dreamboard.local";
|
|
257
|
+
var DEFAULT_ISSUER = "dreamboard-local-harness";
|
|
258
|
+
var DEFAULT_SECRET = "dreamboard-local-harness-token-secret";
|
|
259
|
+
var LOCAL_AWS_ISSUER = "dreamboard-local-aws-harness";
|
|
260
|
+
var LOCAL_AWS_SECRET = "dreamboard-local-aws-harness-token-secret";
|
|
261
|
+
var DEFAULT_TTL_SECONDS = 8 * 60 * 60;
|
|
262
|
+
var mintedTokens = /* @__PURE__ */ new Map();
|
|
263
|
+
function resolveLocalHarnessAccessToken(config) {
|
|
264
|
+
if (IS_PUBLISHED_BUILD || config.environment !== "local") {
|
|
265
|
+
return void 0;
|
|
266
|
+
}
|
|
267
|
+
const profile = inferLocalHarnessProfile(config);
|
|
268
|
+
if (config.authToken && (profile !== "local-aws" || isExplicitTokenSource(config.authTokenSource))) {
|
|
269
|
+
return void 0;
|
|
270
|
+
}
|
|
271
|
+
const cacheKey = [
|
|
272
|
+
profile,
|
|
273
|
+
process.env.LOCAL_HARNESS_TOKEN_ISSUER ?? "",
|
|
274
|
+
process.env.LOCAL_HARNESS_TOKEN_SECRET ?? "",
|
|
275
|
+
process.env.LOCAL_HARNESS_SUBJECT ?? "",
|
|
276
|
+
process.env.HARNESS_USER_EMAIL ?? "",
|
|
277
|
+
process.env.LOCAL_HARNESS_EMAIL ?? "",
|
|
278
|
+
process.env.LOCAL_HARNESS_TOKEN_TTL_SECONDS ?? ""
|
|
279
|
+
].join("\0");
|
|
280
|
+
const cached = mintedTokens.get(cacheKey);
|
|
281
|
+
if (cached) return cached;
|
|
282
|
+
const token = mintLocalHarnessToken(profile);
|
|
283
|
+
mintedTokens.set(cacheKey, token);
|
|
284
|
+
return token;
|
|
285
|
+
}
|
|
286
|
+
function isExplicitTokenSource(source) {
|
|
287
|
+
return source === "flag" || source === "env" || source === "agent-env";
|
|
288
|
+
}
|
|
289
|
+
function inferLocalHarnessProfile(config) {
|
|
290
|
+
return isLocalAwsUrl(config.apiBaseUrl) || isLocalAwsUrl(config.webBaseUrl) ? "local-aws" : "local";
|
|
291
|
+
}
|
|
292
|
+
function mintLocalHarnessToken(profile) {
|
|
293
|
+
const subject = envValue(process.env.LOCAL_HARNESS_SUBJECT) ?? envValue(process.env.HARNESS_USER_EMAIL) ?? DEFAULT_SUBJECT;
|
|
294
|
+
const email = envValue(process.env.LOCAL_HARNESS_EMAIL) ?? (subject.includes("@") ? subject : void 0);
|
|
295
|
+
const issuer = envValue(process.env.LOCAL_HARNESS_TOKEN_ISSUER) ?? (profile === "local-aws" ? LOCAL_AWS_ISSUER : DEFAULT_ISSUER);
|
|
296
|
+
const secret = envValue(process.env.LOCAL_HARNESS_TOKEN_SECRET) ?? (profile === "local-aws" ? LOCAL_AWS_SECRET : DEFAULT_SECRET);
|
|
297
|
+
const ttlSeconds = Number(
|
|
298
|
+
envValue(process.env.LOCAL_HARNESS_TOKEN_TTL_SECONDS) ?? String(DEFAULT_TTL_SECONDS)
|
|
299
|
+
);
|
|
300
|
+
if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"LOCAL_HARNESS_TOKEN_TTL_SECONDS must be a positive number."
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
306
|
+
const payload = {
|
|
307
|
+
typ: "local_harness_access",
|
|
308
|
+
dreamboard_provider: "local-harness",
|
|
309
|
+
dreamboard_provider_subject: subject,
|
|
310
|
+
...email ? { email } : {},
|
|
311
|
+
iss: issuer,
|
|
312
|
+
sub: subject,
|
|
313
|
+
iat: now,
|
|
314
|
+
exp: now + Math.floor(ttlSeconds),
|
|
315
|
+
jti: randomUUID()
|
|
316
|
+
};
|
|
317
|
+
const headerPart = base64UrlJson({ alg: "HS256", typ: "JWT" });
|
|
318
|
+
const payloadPart = base64UrlJson(payload);
|
|
319
|
+
const signature = createHmac("sha256", secret).update(`${headerPart}.${payloadPart}`).digest("base64url");
|
|
320
|
+
return `${headerPart}.${payloadPart}.${signature}`;
|
|
321
|
+
}
|
|
322
|
+
function base64UrlJson(value) {
|
|
323
|
+
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
324
|
+
}
|
|
325
|
+
function envValue(raw) {
|
|
326
|
+
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
327
|
+
}
|
|
328
|
+
function isLocalAwsUrl(rawUrl) {
|
|
329
|
+
if (!rawUrl) return false;
|
|
330
|
+
try {
|
|
331
|
+
const url = new URL(rawUrl);
|
|
332
|
+
return (url.hostname === "localhost" || url.hostname === "127.0.0.1") && (url.port === "18080" || url.port === "8088");
|
|
333
|
+
} catch {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export {
|
|
339
|
+
createUserTokenManager,
|
|
340
|
+
resolveLocalHarnessAccessToken
|
|
341
|
+
};
|
|
342
|
+
//# sourceMappingURL=chunk-CO3BRUD6.mjs.map
|