@tinycloud/cli 0.9.1-beta.5 → 0.9.1-beta.7
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 +8 -0
- package/dist/index.js +556 -140
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +363 -58
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +1 -1
package/dist/legacy-entry.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
tinycloudConfigPath,
|
|
38
38
|
tinycloudHomePath
|
|
39
39
|
} from "@tinycloud/operations/state";
|
|
40
|
-
var CONFIG_DIR, PROFILES_DIR, CONFIG_FILE, DEFAULT_HOST, DEFAULT_OPENKEY_HOST, DEFAULT_PROFILE, DEFAULT_CHAIN_ID, ExitCode;
|
|
40
|
+
var CONFIG_DIR, PROFILES_DIR, CONFIG_FILE, DEFAULT_HOST, DEFAULT_OPENKEY_HOST, DEFAULT_OPENKEY_DEVICE_API_HOST, DEFAULT_PROFILE, DEFAULT_CHAIN_ID, ExitCode;
|
|
41
41
|
var init_constants = __esm({
|
|
42
42
|
"src/config/constants.ts"() {
|
|
43
43
|
"use strict";
|
|
@@ -46,6 +46,7 @@ var init_constants = __esm({
|
|
|
46
46
|
CONFIG_FILE = tinycloudConfigPath();
|
|
47
47
|
DEFAULT_HOST = "https://node.tinycloud.xyz";
|
|
48
48
|
DEFAULT_OPENKEY_HOST = "https://openkey.so";
|
|
49
|
+
DEFAULT_OPENKEY_DEVICE_API_HOST = "https://api.openkey.so";
|
|
49
50
|
DEFAULT_PROFILE = "default";
|
|
50
51
|
DEFAULT_CHAIN_ID = 1;
|
|
51
52
|
ExitCode = {
|
|
@@ -218,6 +219,13 @@ function formatTable(headers, rows) {
|
|
|
218
219
|
);
|
|
219
220
|
return [headerLine, separator, ...dataLines].join("\n");
|
|
220
221
|
}
|
|
222
|
+
function output(data, humanFormatter) {
|
|
223
|
+
if (shouldOutputJson() || !humanFormatter) {
|
|
224
|
+
outputJson(data);
|
|
225
|
+
} else {
|
|
226
|
+
process.stdout.write(humanFormatter() + "\n");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
221
229
|
function formatCheck(ok2, label, detail) {
|
|
222
230
|
const icon = ok2 === "warn" ? theme.warn("\u26A0") : ok2 ? theme.success("\u2713") : theme.error("\u2717");
|
|
223
231
|
const detailStr = detail ? ` ${theme.muted(`(${detail})`)}` : "";
|
|
@@ -6225,7 +6233,7 @@ function eddsa(Point2, cHash, eddsaOpts = {}) {
|
|
|
6225
6233
|
});
|
|
6226
6234
|
const { prehash } = eddsaOpts;
|
|
6227
6235
|
const { BASE, Fp: Fp2, Fn: Fn2 } = Point2;
|
|
6228
|
-
const
|
|
6236
|
+
const randomBytes6 = eddsaOpts.randomBytes || randomBytes;
|
|
6229
6237
|
const adjustScalarBytes2 = eddsaOpts.adjustScalarBytes || ((bytes) => bytes);
|
|
6230
6238
|
const domain = eddsaOpts.domain || ((data, ctx, phflag) => {
|
|
6231
6239
|
_abool2(phflag, "phflag");
|
|
@@ -6307,7 +6315,7 @@ function eddsa(Point2, cHash, eddsaOpts = {}) {
|
|
|
6307
6315
|
signature: 2 * _size,
|
|
6308
6316
|
seed: _size
|
|
6309
6317
|
};
|
|
6310
|
-
function randomSecretKey(seed =
|
|
6318
|
+
function randomSecretKey(seed = randomBytes6(lengths.seed)) {
|
|
6311
6319
|
return _abytes2(seed, lengths.seed, "seed");
|
|
6312
6320
|
}
|
|
6313
6321
|
function keygen(seed) {
|
|
@@ -8989,7 +8997,7 @@ function weierstrass(curveDef) {
|
|
|
8989
8997
|
function prepSig(msgHash, privateKey, opts = defaultSigOpts) {
|
|
8990
8998
|
if (["recovered", "canonical"].some((k) => k in opts))
|
|
8991
8999
|
throw new Error("sign() legacy options not supported");
|
|
8992
|
-
const { hash, randomBytes:
|
|
9000
|
+
const { hash, randomBytes: randomBytes6 } = CURVE;
|
|
8993
9001
|
let { lowS, prehash, extraEntropy: ent } = opts;
|
|
8994
9002
|
if (lowS == null)
|
|
8995
9003
|
lowS = true;
|
|
@@ -9001,7 +9009,7 @@ function weierstrass(curveDef) {
|
|
|
9001
9009
|
const d = normPrivateKeyToScalar(privateKey);
|
|
9002
9010
|
const seedArgs = [int2octets(d), int2octets(h1int)];
|
|
9003
9011
|
if (ent != null && ent !== false) {
|
|
9004
|
-
const e = ent === true ?
|
|
9012
|
+
const e = ent === true ? randomBytes6(Fp2.BYTES) : ent;
|
|
9005
9013
|
seedArgs.push(ensureBytes2("extraEntropy", e));
|
|
9006
9014
|
}
|
|
9007
9015
|
const seed = concatBytes3(...seedArgs);
|
|
@@ -14663,7 +14671,7 @@ var init_dist2 = __esm({
|
|
|
14663
14671
|
async handleErrorResponse(response, operation) {
|
|
14664
14672
|
const errorText = await response.text();
|
|
14665
14673
|
const errorBody = parseServiceErrorBody(errorText);
|
|
14666
|
-
const
|
|
14674
|
+
const errorCode2 = this.mapHttpStatusToErrorCode(
|
|
14667
14675
|
response.status,
|
|
14668
14676
|
errorBody.error
|
|
14669
14677
|
);
|
|
@@ -14681,7 +14689,7 @@ var init_dist2 = __esm({
|
|
|
14681
14689
|
if (resource) meta.resource = resource;
|
|
14682
14690
|
}
|
|
14683
14691
|
return err(
|
|
14684
|
-
serviceError(
|
|
14692
|
+
serviceError(errorCode2, message, "sql", { meta })
|
|
14685
14693
|
);
|
|
14686
14694
|
}
|
|
14687
14695
|
mapHttpStatusToErrorCode(status, serverError) {
|
|
@@ -15011,7 +15019,7 @@ var init_dist2 = __esm({
|
|
|
15011
15019
|
async handleErrorResponse(response, operation) {
|
|
15012
15020
|
const errorText = await response.text();
|
|
15013
15021
|
const errorBody = parseServiceErrorBody(errorText);
|
|
15014
|
-
const
|
|
15022
|
+
const errorCode2 = this.mapHttpStatusToErrorCode(
|
|
15015
15023
|
response.status,
|
|
15016
15024
|
errorBody.error
|
|
15017
15025
|
);
|
|
@@ -15029,7 +15037,7 @@ var init_dist2 = __esm({
|
|
|
15029
15037
|
if (resource) meta.resource = resource;
|
|
15030
15038
|
}
|
|
15031
15039
|
return err(
|
|
15032
|
-
serviceError(
|
|
15040
|
+
serviceError(errorCode2, message, "duckdb", { meta })
|
|
15033
15041
|
);
|
|
15034
15042
|
}
|
|
15035
15043
|
mapHttpStatusToErrorCode(status, serverError) {
|
|
@@ -18210,19 +18218,19 @@ function hex(bytes3) {
|
|
|
18210
18218
|
}
|
|
18211
18219
|
function base32Lower(bytes3) {
|
|
18212
18220
|
const alphabet2 = "abcdefghijklmnopqrstuvwxyz234567";
|
|
18213
|
-
let
|
|
18221
|
+
let output2 = "";
|
|
18214
18222
|
let buffer = 0;
|
|
18215
18223
|
let bits = 0;
|
|
18216
18224
|
for (const byte of bytes3) {
|
|
18217
18225
|
buffer = buffer << 8 | byte;
|
|
18218
18226
|
bits += 8;
|
|
18219
18227
|
while (bits >= 5) {
|
|
18220
|
-
|
|
18228
|
+
output2 += alphabet2[buffer >>> bits - 5 & 31];
|
|
18221
18229
|
bits -= 5;
|
|
18222
18230
|
}
|
|
18223
18231
|
}
|
|
18224
|
-
if (bits > 0)
|
|
18225
|
-
return
|
|
18232
|
+
if (bits > 0) output2 += alphabet2[buffer << 5 - bits & 31];
|
|
18233
|
+
return output2;
|
|
18226
18234
|
}
|
|
18227
18235
|
function assertCanonicalCid(cidString) {
|
|
18228
18236
|
const cid2 = CID.parse(cidString);
|
|
@@ -25367,13 +25375,13 @@ function concat2(arrays, length22) {
|
|
|
25367
25375
|
if (length22 == null) {
|
|
25368
25376
|
length22 = arrays.reduce((acc, curr) => acc + curr.length, 0);
|
|
25369
25377
|
}
|
|
25370
|
-
const
|
|
25378
|
+
const output2 = allocUnsafe(length22);
|
|
25371
25379
|
let offset = 0;
|
|
25372
25380
|
for (const arr of arrays) {
|
|
25373
|
-
|
|
25381
|
+
output2.set(arr, offset);
|
|
25374
25382
|
offset += arr.length;
|
|
25375
25383
|
}
|
|
25376
|
-
return asUint8Array(
|
|
25384
|
+
return asUint8Array(output2);
|
|
25377
25385
|
}
|
|
25378
25386
|
function createCodec(name2, prefix, encode52, decode72) {
|
|
25379
25387
|
return {
|
|
@@ -30963,6 +30971,19 @@ Open this URL in a browser to authenticate:
|
|
|
30963
30971
|
});
|
|
30964
30972
|
}
|
|
30965
30973
|
|
|
30974
|
+
// src/auth/device-auth.ts
|
|
30975
|
+
init_constants();
|
|
30976
|
+
init_profiles();
|
|
30977
|
+
import {
|
|
30978
|
+
createDecipheriv,
|
|
30979
|
+
createHash,
|
|
30980
|
+
createHmac,
|
|
30981
|
+
createPublicKey,
|
|
30982
|
+
diffieHellman,
|
|
30983
|
+
generateKeyPairSync,
|
|
30984
|
+
randomBytes as randomBytes3
|
|
30985
|
+
} from "crypto";
|
|
30986
|
+
|
|
30966
30987
|
// src/auth/local-key.ts
|
|
30967
30988
|
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
30968
30989
|
import { PrivateKeySigner } from "@tinycloud/node-sdk";
|
|
@@ -31033,6 +31054,251 @@ async function localKeySignIn(options) {
|
|
|
31033
31054
|
};
|
|
31034
31055
|
}
|
|
31035
31056
|
|
|
31057
|
+
// src/auth/device-auth.ts
|
|
31058
|
+
var SHARE_DEVICE_DELEGATION_SECONDS = 30 * 24 * 60 * 60;
|
|
31059
|
+
var SHARE_DEVICE_PERMISSIONS = [{
|
|
31060
|
+
service: "tinycloud.capabilities",
|
|
31061
|
+
space: "applications",
|
|
31062
|
+
path: "",
|
|
31063
|
+
actions: ["tinycloud.capabilities/read"]
|
|
31064
|
+
}];
|
|
31065
|
+
function digest2(value) {
|
|
31066
|
+
return createHash("sha256").update(value).digest("base64url");
|
|
31067
|
+
}
|
|
31068
|
+
function canonicalOrigin(value, label) {
|
|
31069
|
+
const url = new URL(value);
|
|
31070
|
+
const loopback = url.hostname === "127.0.0.1" || url.hostname === "localhost";
|
|
31071
|
+
if (url.origin !== value || url.protocol !== "https:" && !(loopback && url.protocol === "http:")) {
|
|
31072
|
+
throw new Error(`${label} must be a canonical HTTPS origin`);
|
|
31073
|
+
}
|
|
31074
|
+
return value;
|
|
31075
|
+
}
|
|
31076
|
+
function jsonEqual(left, right) {
|
|
31077
|
+
const canonical = (value) => Array.isArray(value) ? value.map(canonical) : value && typeof value === "object" ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, canonical(entry)])) : value;
|
|
31078
|
+
return JSON.stringify(canonical(left)) === JSON.stringify(canonical(right));
|
|
31079
|
+
}
|
|
31080
|
+
function validateStart(value) {
|
|
31081
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("OpenKey returned an invalid device authorization response");
|
|
31082
|
+
const result = value;
|
|
31083
|
+
if (typeof result.transactionId !== "string" || !/^[A-Za-z0-9_-]{20,}$/.test(result.transactionId) || typeof result.userCode !== "string" || !/^[A-Z2-9]{4}-[A-Z2-9]{4}$/.test(result.userCode) || typeof result.verificationUri !== "string" || typeof result.verificationUriComplete !== "string" || !Number.isSafeInteger(result.expiresIn) || Number(result.expiresIn) < 60 || !Number.isSafeInteger(result.interval) || Number(result.interval) < 1) throw new Error("OpenKey returned an invalid device authorization response");
|
|
31084
|
+
canonicalOrigin(new URL(result.verificationUri).origin, "verification URI");
|
|
31085
|
+
if (new URL(result.verificationUriComplete).origin !== new URL(result.verificationUri).origin) {
|
|
31086
|
+
throw new Error("OpenKey returned an invalid verification URI");
|
|
31087
|
+
}
|
|
31088
|
+
return result;
|
|
31089
|
+
}
|
|
31090
|
+
async function responseJson(response) {
|
|
31091
|
+
try {
|
|
31092
|
+
return await response.json();
|
|
31093
|
+
} catch {
|
|
31094
|
+
throw new Error(`OpenKey device authorization failed (HTTP ${response.status})`);
|
|
31095
|
+
}
|
|
31096
|
+
}
|
|
31097
|
+
function errorCode(value) {
|
|
31098
|
+
return value && typeof value === "object" && typeof value.error === "string" ? value.error : void 0;
|
|
31099
|
+
}
|
|
31100
|
+
function publicSessionJwk(value) {
|
|
31101
|
+
const publicJwk = publicJwkForDelegation(value);
|
|
31102
|
+
const record = publicJwk;
|
|
31103
|
+
if (record.kty !== "OKP" || record.crv !== "Ed25519" || typeof record.x !== "string") {
|
|
31104
|
+
throw new Error("CLI session key is not a public Ed25519 JWK");
|
|
31105
|
+
}
|
|
31106
|
+
return publicJwk;
|
|
31107
|
+
}
|
|
31108
|
+
function publicRelayJwk(value) {
|
|
31109
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("OpenKey returned an invalid relay key");
|
|
31110
|
+
const jwk = value;
|
|
31111
|
+
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(jwk.x) || typeof jwk.y !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(jwk.y) || "d" in jwk) throw new Error("OpenKey returned an invalid relay key");
|
|
31112
|
+
return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
|
|
31113
|
+
}
|
|
31114
|
+
function decodeCanonicalBase64Url(value, label) {
|
|
31115
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new Error(`OpenKey returned an invalid ${label}`);
|
|
31116
|
+
const decoded = Buffer.from(value, "base64url");
|
|
31117
|
+
if (decoded.toString("base64url") !== value) throw new Error(`OpenKey returned an invalid ${label}`);
|
|
31118
|
+
return decoded;
|
|
31119
|
+
}
|
|
31120
|
+
function deriveRelayKey(sharedSecret, transactionId) {
|
|
31121
|
+
const extracted = createHmac("sha256", Buffer.from(transactionId)).update(sharedSecret).digest();
|
|
31122
|
+
return createHmac("sha256", extracted).update(Buffer.from("openkey-device-relay-v1")).update(Buffer.from([1])).digest();
|
|
31123
|
+
}
|
|
31124
|
+
function decryptRelayResult(envelope, transactionId, privateKey) {
|
|
31125
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) throw new Error("OpenKey returned an invalid encrypted relay result");
|
|
31126
|
+
const relay = envelope;
|
|
31127
|
+
if (relay.version !== 1 || relay.algorithm !== "ECDH-P256-A256GCM") throw new Error("OpenKey returned an unsupported encrypted relay result");
|
|
31128
|
+
const peer = createPublicKey({ key: publicRelayJwk(relay.ephemeralPublicJwk), format: "jwk" });
|
|
31129
|
+
const nonce = decodeCanonicalBase64Url(relay.nonce, "relay nonce");
|
|
31130
|
+
const ciphertext = decodeCanonicalBase64Url(relay.ciphertext, "relay ciphertext");
|
|
31131
|
+
if (nonce.length !== 12 || ciphertext.length <= 16) throw new Error("OpenKey returned an invalid encrypted relay result");
|
|
31132
|
+
const sharedSecret = diffieHellman({ privateKey, publicKey: peer });
|
|
31133
|
+
const key = deriveRelayKey(sharedSecret, transactionId);
|
|
31134
|
+
const decipher = createDecipheriv("aes-256-gcm", key, nonce);
|
|
31135
|
+
decipher.setAAD(Buffer.from(transactionId));
|
|
31136
|
+
decipher.setAuthTag(ciphertext.subarray(ciphertext.length - 16));
|
|
31137
|
+
let value;
|
|
31138
|
+
try {
|
|
31139
|
+
value = JSON.parse(Buffer.concat([decipher.update(ciphertext.subarray(0, -16)), decipher.final()]).toString("utf8"));
|
|
31140
|
+
} catch {
|
|
31141
|
+
throw new Error("OpenKey returned an unreadable encrypted relay result");
|
|
31142
|
+
}
|
|
31143
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("OpenKey returned an invalid encrypted relay result");
|
|
31144
|
+
return value;
|
|
31145
|
+
}
|
|
31146
|
+
function assertShareDelegationPermissions(value) {
|
|
31147
|
+
if (!Array.isArray(value) || value.length !== 1) throw new Error("OpenKey returned a delegation outside the requested Share scope");
|
|
31148
|
+
const permission = value[0];
|
|
31149
|
+
if (!permission || permission.service !== "tinycloud.capabilities" && permission.service !== "capabilities" || permission.space !== "applications" && !(typeof permission.space === "string" && permission.space.endsWith(":applications")) || permission.path !== "" || !Array.isArray(permission.actions) || permission.actions.length !== 1 || permission.actions[0] !== "tinycloud.capabilities/read") throw new Error("OpenKey returned a delegation outside the requested Share scope");
|
|
31150
|
+
}
|
|
31151
|
+
function assertApprovedBinding(input) {
|
|
31152
|
+
if (input.binding.transactionId !== input.transactionId || input.binding.sessionDid !== input.sessionDid || input.binding.nodeOrigin !== input.nodeOrigin || input.binding.shareOrigin !== input.shareOrigin || !jsonEqual(input.binding.permissions, SHARE_DEVICE_PERMISSIONS)) throw new Error("OpenKey returned a delegation with the wrong device binding");
|
|
31153
|
+
const expiresAt = Date.parse(input.binding.delegationExpiresAt);
|
|
31154
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || expiresAt > Date.now() + SHARE_DEVICE_DELEGATION_SECONDS * 1e3 + 3e4) {
|
|
31155
|
+
throw new Error("OpenKey returned a delegation outside the requested expiry window");
|
|
31156
|
+
}
|
|
31157
|
+
if (input.delegation.verificationMethod !== input.sessionDid) {
|
|
31158
|
+
throw new Error("OpenKey returned a delegation for a different CLI session DID");
|
|
31159
|
+
}
|
|
31160
|
+
assertShareDelegationPermissions(input.delegation.permissions);
|
|
31161
|
+
const delegationExpiryValue = input.delegation.expiresAt ?? input.delegation.expirationTime ?? input.delegation.expiry;
|
|
31162
|
+
const delegationExpiresAt = typeof delegationExpiryValue === "string" ? Date.parse(delegationExpiryValue) : Number.NaN;
|
|
31163
|
+
if (!Number.isFinite(delegationExpiresAt) || delegationExpiresAt !== expiresAt) {
|
|
31164
|
+
throw new Error("OpenKey returned a delegation outside the approved expiry window");
|
|
31165
|
+
}
|
|
31166
|
+
if (!input.delegation.jwk || typeof input.delegation.jwk !== "object" || !jsonEqual(publicSessionJwk(input.delegation.jwk), input.publicJwk)) {
|
|
31167
|
+
throw new Error("OpenKey returned a delegation for a different CLI session key");
|
|
31168
|
+
}
|
|
31169
|
+
const invalid = validateDelegationCallbackPayload(input.delegation);
|
|
31170
|
+
if (invalid) throw new Error(`OpenKey returned an invalid delegation: ${invalid}`);
|
|
31171
|
+
}
|
|
31172
|
+
async function acquireShareDeviceDelegation(input) {
|
|
31173
|
+
const openkeyHost = canonicalOrigin(input.openkeyHost ?? DEFAULT_OPENKEY_DEVICE_API_HOST, "OpenKey host");
|
|
31174
|
+
const nodeOrigin = canonicalOrigin(input.nodeOrigin, "TinyCloud node origin");
|
|
31175
|
+
const shareOrigin = canonicalOrigin(input.shareOrigin, "Share origin");
|
|
31176
|
+
const fetchFn = input.fetchFn ?? globalThis.fetch;
|
|
31177
|
+
const deviceSecret = randomBytes3(32).toString("base64url");
|
|
31178
|
+
const codeVerifier = randomBytes3(32).toString("base64url");
|
|
31179
|
+
const relayKeys = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
31180
|
+
const relayPublicJwk = publicRelayJwk(relayKeys.publicKey.export({ format: "jwk" }));
|
|
31181
|
+
const publicJwk = publicSessionJwk(input.jwk);
|
|
31182
|
+
const startResponse = await fetchFn(`${openkeyHost}/api/device-authorizations`, {
|
|
31183
|
+
method: "POST",
|
|
31184
|
+
credentials: "omit",
|
|
31185
|
+
redirect: "error",
|
|
31186
|
+
referrerPolicy: "no-referrer",
|
|
31187
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
31188
|
+
body: JSON.stringify({
|
|
31189
|
+
deviceSecretHash: digest2(deviceSecret),
|
|
31190
|
+
codeChallenge: digest2(codeVerifier),
|
|
31191
|
+
relayPublicJwk,
|
|
31192
|
+
sessionDid: input.sessionDid,
|
|
31193
|
+
publicJwk,
|
|
31194
|
+
permissions: SHARE_DEVICE_PERMISSIONS,
|
|
31195
|
+
nodeOrigin,
|
|
31196
|
+
shareOrigin,
|
|
31197
|
+
delegationTtlSeconds: SHARE_DEVICE_DELEGATION_SECONDS
|
|
31198
|
+
})
|
|
31199
|
+
});
|
|
31200
|
+
const startValue = await responseJson(startResponse);
|
|
31201
|
+
if (!startResponse.ok) throw new Error(`OpenKey device authorization failed: ${errorCode(startValue) ?? startResponse.status}`);
|
|
31202
|
+
const started = validateStart(startValue);
|
|
31203
|
+
(input.emitInstructions ?? ((value) => {
|
|
31204
|
+
process.stderr.write(`OpenKey device authorization
|
|
31205
|
+
Visit: ${value.verificationUri}
|
|
31206
|
+
Code: ${value.userCode}
|
|
31207
|
+
|
|
31208
|
+
Waiting for approval\u2026
|
|
31209
|
+
`);
|
|
31210
|
+
}))({ verificationUri: started.verificationUri, verificationUriComplete: started.verificationUriComplete, userCode: started.userCode });
|
|
31211
|
+
const deadline = Date.now() + started.expiresIn * 1e3;
|
|
31212
|
+
let interval = started.interval;
|
|
31213
|
+
const wait = input.wait ?? ((milliseconds) => new Promise((resolve4) => setTimeout(resolve4, milliseconds)));
|
|
31214
|
+
while (Date.now() < deadline) {
|
|
31215
|
+
await wait(interval * 1e3);
|
|
31216
|
+
const response = await fetchFn(`${openkeyHost}/api/device-authorizations/token`, {
|
|
31217
|
+
method: "POST",
|
|
31218
|
+
credentials: "omit",
|
|
31219
|
+
redirect: "error",
|
|
31220
|
+
referrerPolicy: "no-referrer",
|
|
31221
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
31222
|
+
body: JSON.stringify({ transactionId: started.transactionId, deviceSecret, codeVerifier })
|
|
31223
|
+
});
|
|
31224
|
+
const value = await responseJson(response);
|
|
31225
|
+
const code3 = errorCode(value);
|
|
31226
|
+
if (response.status === 429 && code3 === "slow_down") {
|
|
31227
|
+
interval += 1;
|
|
31228
|
+
continue;
|
|
31229
|
+
}
|
|
31230
|
+
if (!response.ok) throw new Error(`OpenKey device authorization failed: ${code3 ?? response.status}`);
|
|
31231
|
+
const result = value;
|
|
31232
|
+
if (result.status === "pending") {
|
|
31233
|
+
interval = Math.max(interval, result.interval);
|
|
31234
|
+
continue;
|
|
31235
|
+
}
|
|
31236
|
+
if (result.status !== "approved" || !result.relay || !result.binding) {
|
|
31237
|
+
throw new Error("OpenKey returned an invalid device authorization result");
|
|
31238
|
+
}
|
|
31239
|
+
const delegation = decryptRelayResult(result.relay, started.transactionId, relayKeys.privateKey);
|
|
31240
|
+
assertApprovedBinding({ binding: result.binding, transactionId: started.transactionId, sessionDid: input.sessionDid, nodeOrigin, shareOrigin, publicJwk, delegation });
|
|
31241
|
+
return delegation;
|
|
31242
|
+
}
|
|
31243
|
+
throw new Error("OpenKey device authorization expired before approval");
|
|
31244
|
+
}
|
|
31245
|
+
function mergePrivateJwkIntoSession(session, key) {
|
|
31246
|
+
const sessionJwk = session.jwk;
|
|
31247
|
+
if (!sessionJwk || typeof sessionJwk !== "object") return session;
|
|
31248
|
+
const sessionJwkRecord = sessionJwk;
|
|
31249
|
+
if (typeof sessionJwkRecord.d === "string" && sessionJwkRecord.d.length > 0) return session;
|
|
31250
|
+
const privateParameter = key.d;
|
|
31251
|
+
if (typeof privateParameter !== "string" || privateParameter.length === 0) return session;
|
|
31252
|
+
return { ...session, jwk: { ...sessionJwkRecord, d: privateParameter } };
|
|
31253
|
+
}
|
|
31254
|
+
async function ensureShareDeviceAuthorization(input) {
|
|
31255
|
+
let profile = await ProfileManager.getProfile(input.profileName).catch(() => null);
|
|
31256
|
+
if (profile?.authMethod === "local" && input.allowReplaceLocal !== true) {
|
|
31257
|
+
throw new Error("This profile uses a local owner key. Run `tc auth login --device` explicitly to replace its authentication posture.");
|
|
31258
|
+
}
|
|
31259
|
+
let key = await ProfileManager.getKey(input.profileName);
|
|
31260
|
+
if (!key) {
|
|
31261
|
+
const generated = generateKey2();
|
|
31262
|
+
key = generated.jwk;
|
|
31263
|
+
await ProfileManager.setKey(input.profileName, key);
|
|
31264
|
+
}
|
|
31265
|
+
const sessionDid = keyToDID(key);
|
|
31266
|
+
profile = {
|
|
31267
|
+
...profile,
|
|
31268
|
+
name: input.profileName,
|
|
31269
|
+
host: input.nodeOrigin,
|
|
31270
|
+
chainId: profile?.chainId ?? DEFAULT_CHAIN_ID,
|
|
31271
|
+
spaceName: profile?.spaceName ?? "applications",
|
|
31272
|
+
did: sessionDid,
|
|
31273
|
+
sessionDid,
|
|
31274
|
+
createdAt: profile?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31275
|
+
posture: "owner-openkey",
|
|
31276
|
+
operatorType: profile?.operatorType ?? "human",
|
|
31277
|
+
authMethod: "openkey",
|
|
31278
|
+
openkeyHost: input.openkeyHost ?? profile?.openkeyHost
|
|
31279
|
+
};
|
|
31280
|
+
await ProfileManager.setProfile(input.profileName, profile);
|
|
31281
|
+
const delegation = await acquireShareDeviceDelegation({
|
|
31282
|
+
sessionDid,
|
|
31283
|
+
jwk: key,
|
|
31284
|
+
nodeOrigin: input.nodeOrigin,
|
|
31285
|
+
shareOrigin: input.shareOrigin,
|
|
31286
|
+
openkeyHost: input.openkeyHost ?? profile.openkeyHost,
|
|
31287
|
+
fetchFn: input.fetchFn,
|
|
31288
|
+
emitInstructions: input.emitInstructions,
|
|
31289
|
+
wait: input.wait
|
|
31290
|
+
});
|
|
31291
|
+
const session = mergePrivateJwkIntoSession(delegation, key);
|
|
31292
|
+
await ProfileManager.setSession(input.profileName, session);
|
|
31293
|
+
const updatedProfile = {
|
|
31294
|
+
...profile,
|
|
31295
|
+
ownerDid: typeof session.ownerDid === "string" ? session.ownerDid : profile.ownerDid,
|
|
31296
|
+
spaceId: typeof session.spaceId === "string" ? session.spaceId : profile.spaceId
|
|
31297
|
+
};
|
|
31298
|
+
await ProfileManager.setProfile(input.profileName, updatedProfile);
|
|
31299
|
+
return { profile: updatedProfile, delegation: session };
|
|
31300
|
+
}
|
|
31301
|
+
|
|
31036
31302
|
// src/commands/auth.ts
|
|
31037
31303
|
init_theme();
|
|
31038
31304
|
function resolveOpenKeyHost(profile) {
|
|
@@ -31040,7 +31306,7 @@ function resolveOpenKeyHost(profile) {
|
|
|
31040
31306
|
}
|
|
31041
31307
|
async function promptAuthMethod() {
|
|
31042
31308
|
if (!isInteractive()) {
|
|
31043
|
-
return "
|
|
31309
|
+
return "openkey";
|
|
31044
31310
|
}
|
|
31045
31311
|
const rl = createInterface2({
|
|
31046
31312
|
input: process.stdin,
|
|
@@ -31066,12 +31332,20 @@ async function promptAuthMethod() {
|
|
|
31066
31332
|
}
|
|
31067
31333
|
function registerAuthCommand(program) {
|
|
31068
31334
|
const auth = program.command("auth").description("Authentication management");
|
|
31069
|
-
auth.command("login").description("Authenticate with TinyCloud").option("--paste", "Use manual paste mode instead of browser callback").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").action(async (options, cmd) => {
|
|
31335
|
+
auth.command("login").description("Authenticate with TinyCloud").option("--device", "Use OpenKey device authorization (recommended for remote/headless use)").option("--paste", "Use manual paste mode instead of browser callback").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").action(async (options, cmd) => {
|
|
31070
31336
|
try {
|
|
31337
|
+
if (options.device && options.paste) {
|
|
31338
|
+
throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
|
|
31339
|
+
}
|
|
31071
31340
|
const globalOpts = cmd.optsWithGlobals();
|
|
31072
31341
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
31073
31342
|
let method;
|
|
31074
|
-
if (options.method) {
|
|
31343
|
+
if (options.device && options.method === "local") {
|
|
31344
|
+
throw new CLIError("INVALID_ARGUMENT", "--device requires --method openkey.", ExitCode.USAGE_ERROR);
|
|
31345
|
+
}
|
|
31346
|
+
if (options.device) {
|
|
31347
|
+
method = "openkey";
|
|
31348
|
+
} else if (options.method) {
|
|
31075
31349
|
if (options.method !== "local" && options.method !== "openkey") {
|
|
31076
31350
|
throw new CLIError(
|
|
31077
31351
|
"INVALID_METHOD",
|
|
@@ -31088,7 +31362,8 @@ function registerAuthCommand(program) {
|
|
|
31088
31362
|
} else {
|
|
31089
31363
|
await handleOpenKeyAuth(ctx.profile, ctx.host, {
|
|
31090
31364
|
paste: options.paste,
|
|
31091
|
-
noPopup: options.popup === false
|
|
31365
|
+
noPopup: options.popup === false,
|
|
31366
|
+
device: options.device === true || !isInteractive() && options.paste !== true
|
|
31092
31367
|
});
|
|
31093
31368
|
}
|
|
31094
31369
|
} catch (error) {
|
|
@@ -31676,15 +31951,15 @@ async function importRequestBoundDelegation(ctx, artifact) {
|
|
|
31676
31951
|
);
|
|
31677
31952
|
switch (result.status) {
|
|
31678
31953
|
case "ok": {
|
|
31679
|
-
const
|
|
31954
|
+
const output2 = result.output;
|
|
31680
31955
|
outputJson({
|
|
31681
31956
|
imported: true,
|
|
31682
|
-
activated:
|
|
31957
|
+
activated: output2.activated,
|
|
31683
31958
|
kind: "tinycloud.auth.delegation",
|
|
31684
31959
|
requestId: typeof artifact === "object" && artifact !== null && typeof artifact.requestId === "string" ? artifact.requestId : null,
|
|
31685
|
-
delegationCid:
|
|
31686
|
-
permissions:
|
|
31687
|
-
expiry:
|
|
31960
|
+
delegationCid: output2.cid,
|
|
31961
|
+
permissions: output2.effectivePermissions,
|
|
31962
|
+
expiry: output2.expiry
|
|
31688
31963
|
});
|
|
31689
31964
|
return;
|
|
31690
31965
|
}
|
|
@@ -32183,6 +32458,24 @@ async function handleLocalAuth(profileName, host, options = {}) {
|
|
|
32183
32458
|
return { profile: updatedProfile, sessionResult };
|
|
32184
32459
|
}
|
|
32185
32460
|
async function handleOpenKeyAuth(profileName, host, options = {}) {
|
|
32461
|
+
if (options.device) {
|
|
32462
|
+
const result = await ensureShareDeviceAuthorization({
|
|
32463
|
+
profileName,
|
|
32464
|
+
nodeOrigin: host,
|
|
32465
|
+
shareOrigin: "https://share.tinycloud.xyz",
|
|
32466
|
+
openkeyHost: process.env.TC_OPENKEY_HOST,
|
|
32467
|
+
allowReplaceLocal: true
|
|
32468
|
+
});
|
|
32469
|
+
outputJson({
|
|
32470
|
+
authenticated: true,
|
|
32471
|
+
profile: profileName,
|
|
32472
|
+
did: result.profile.did,
|
|
32473
|
+
spaceId: result.profile.spaceId ?? null,
|
|
32474
|
+
authMethod: "openkey",
|
|
32475
|
+
mode: "device"
|
|
32476
|
+
});
|
|
32477
|
+
return;
|
|
32478
|
+
}
|
|
32186
32479
|
const { profile, delegationData } = await refreshOpenKeySession(profileName, host, options);
|
|
32187
32480
|
outputJson({
|
|
32188
32481
|
authenticated: true,
|
|
@@ -32192,25 +32485,6 @@ async function handleOpenKeyAuth(profileName, host, options = {}) {
|
|
|
32192
32485
|
authMethod: "openkey"
|
|
32193
32486
|
});
|
|
32194
32487
|
}
|
|
32195
|
-
function mergePrivateJwkIntoSession(session, key) {
|
|
32196
|
-
const sessionJwk = session.jwk;
|
|
32197
|
-
if (!sessionJwk || typeof sessionJwk !== "object") {
|
|
32198
|
-
return session;
|
|
32199
|
-
}
|
|
32200
|
-
const sessionJwkRecord = sessionJwk;
|
|
32201
|
-
const sessionD = sessionJwkRecord.d;
|
|
32202
|
-
if (typeof sessionD === "string" && sessionD.length > 0) {
|
|
32203
|
-
return session;
|
|
32204
|
-
}
|
|
32205
|
-
const keyD = key.d;
|
|
32206
|
-
if (typeof keyD !== "string" || keyD.length === 0) {
|
|
32207
|
-
return session;
|
|
32208
|
-
}
|
|
32209
|
-
return {
|
|
32210
|
-
...session,
|
|
32211
|
-
jwk: { ...sessionJwkRecord, d: keyD }
|
|
32212
|
-
};
|
|
32213
|
-
}
|
|
32214
32488
|
async function refreshOpenKeySession(profileName, host, options = {}) {
|
|
32215
32489
|
const key = await ProfileManager.getKey(profileName);
|
|
32216
32490
|
if (!key) {
|
|
@@ -32786,6 +33060,36 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
32786
33060
|
});
|
|
32787
33061
|
}
|
|
32788
33062
|
|
|
33063
|
+
// src/commands/enable.ts
|
|
33064
|
+
init_profiles();
|
|
33065
|
+
init_errors();
|
|
33066
|
+
init_formatter();
|
|
33067
|
+
function registerEnableCommand(program) {
|
|
33068
|
+
const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
|
|
33069
|
+
enable.command("share").description("Approve Share publishing through OpenKey device authorization").action(async (_options, command) => {
|
|
33070
|
+
try {
|
|
33071
|
+
const context = await ProfileManager.resolveContext(command.optsWithGlobals());
|
|
33072
|
+
const result = await ensureShareDeviceAuthorization({
|
|
33073
|
+
profileName: context.profile,
|
|
33074
|
+
nodeOrigin: context.host,
|
|
33075
|
+
shareOrigin: "https://share.tinycloud.xyz",
|
|
33076
|
+
openkeyHost: process.env.TC_OPENKEY_HOST,
|
|
33077
|
+
allowReplaceLocal: true
|
|
33078
|
+
});
|
|
33079
|
+
const value = {
|
|
33080
|
+
enabled: true,
|
|
33081
|
+
service: "share",
|
|
33082
|
+
profile: context.profile,
|
|
33083
|
+
sessionDid: result.profile.sessionDid ?? result.profile.did,
|
|
33084
|
+
expiresAt: result.delegation.expiresAt ?? result.delegation.expirationTime ?? result.delegation.expiry
|
|
33085
|
+
};
|
|
33086
|
+
output(value, () => `Share enabled for profile ${context.profile}.`);
|
|
33087
|
+
} catch (error) {
|
|
33088
|
+
handleError(error);
|
|
33089
|
+
}
|
|
33090
|
+
});
|
|
33091
|
+
}
|
|
33092
|
+
|
|
32789
33093
|
// src/commands/init.ts
|
|
32790
33094
|
init_profiles();
|
|
32791
33095
|
init_formatter();
|
|
@@ -34521,7 +34825,7 @@ function receiveJson(result, path) {
|
|
|
34521
34825
|
// src/share/io.ts
|
|
34522
34826
|
import { constants } from "fs";
|
|
34523
34827
|
import { lstat, mkdir as mkdir3, mkdtemp, open as open3, readFile as readFile9, realpath, stat as stat2, link, rename as rename2, rm as rm2, unlink } from "fs/promises";
|
|
34524
|
-
import { randomBytes as
|
|
34828
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
34525
34829
|
import { basename as basename2, join as join6, resolve as resolve2, sep } from "path";
|
|
34526
34830
|
var MAX_SHARE_STDIN_BYTES = 100 * 1024 * 1024;
|
|
34527
34831
|
var MAX_SHARE_URL_BYTES = 64 * 1024;
|
|
@@ -34600,7 +34904,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
|
|
|
34600
34904
|
const stagingDirectory = await mkdtemp(join6(stableDirectory, ".tinycloud-share-stage-"));
|
|
34601
34905
|
const stagingInfo = await lstat(stagingDirectory);
|
|
34602
34906
|
if (!stagingInfo.isDirectory() || (stagingInfo.mode & 511) !== 448) throw new Error("OUTPUT_EXISTS");
|
|
34603
|
-
const stagingPath = join6(stagingDirectory, `.tinycloud-share-${
|
|
34907
|
+
const stagingPath = join6(stagingDirectory, `.tinycloud-share-${randomBytes4(16).toString("hex")}.tmp`);
|
|
34604
34908
|
let temporaryPath;
|
|
34605
34909
|
let handle;
|
|
34606
34910
|
try {
|
|
@@ -34653,11 +34957,11 @@ function parseShareTarget(value) {
|
|
|
34653
34957
|
if (value.includes("@")) return { kind: "email", address: value };
|
|
34654
34958
|
throw new CLIError("INVALID_ARGUMENT", "--to must be anyone, a did:, an email address, or domain:example.com", 2);
|
|
34655
34959
|
}
|
|
34656
|
-
function publishServices() {
|
|
34960
|
+
function publishServices(insecureLocalRegistry = false) {
|
|
34657
34961
|
return {
|
|
34658
34962
|
...shareServices.uploadBlob === void 0 ? {} : { uploadBlob: shareServices.uploadBlob },
|
|
34659
34963
|
...shareServices.authorizeUpload === void 0 ? {} : { authorizeUpload: shareServices.authorizeUpload },
|
|
34660
|
-
...shareServices.authorizeUpload === void 0 ? {} : { authorizationOrigin: SHARE_ORIGIN },
|
|
34964
|
+
...shareServices.authorizeUpload === void 0 || insecureLocalRegistry ? {} : { authorizationOrigin: SHARE_ORIGIN },
|
|
34661
34965
|
...shareServices.credentials === void 0 ? {} : { credentials: shareServices.credentials },
|
|
34662
34966
|
...shareServices.fetchFn === void 0 ? {} : { fetchFn: shareServices.fetchFn }
|
|
34663
34967
|
};
|
|
@@ -34787,7 +35091,7 @@ function registerShareCommand(program) {
|
|
|
34787
35091
|
allowInsecureRegistry: options.insecureRegistry === true,
|
|
34788
35092
|
notify: options.notify === true,
|
|
34789
35093
|
targetAdapter: shareServices.targetAdapter,
|
|
34790
|
-
...publishServices()
|
|
35094
|
+
...publishServices(options.insecureRegistry === true)
|
|
34791
35095
|
});
|
|
34792
35096
|
if ("state" in result) {
|
|
34793
35097
|
if (json) {
|
|
@@ -34834,9 +35138,9 @@ function registerShareCommand(program) {
|
|
|
34834
35138
|
process.stdout.write(Buffer.from(bytes));
|
|
34835
35139
|
return;
|
|
34836
35140
|
}
|
|
34837
|
-
const
|
|
34838
|
-
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, legacy: true, path:
|
|
34839
|
-
else receiveHuman(
|
|
35141
|
+
const output3 = await writeShareOutput(options.output ?? ".", "share.md", bytes, options.force === true);
|
|
35142
|
+
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, legacy: true, path: output3 });
|
|
35143
|
+
else receiveHuman(output3);
|
|
34840
35144
|
return;
|
|
34841
35145
|
}
|
|
34842
35146
|
const result = await receiveShare(link2, {
|
|
@@ -34860,9 +35164,9 @@ function registerShareCommand(program) {
|
|
|
34860
35164
|
process.stdout.write(Buffer.from(result.bytes));
|
|
34861
35165
|
return;
|
|
34862
35166
|
}
|
|
34863
|
-
const
|
|
34864
|
-
if (json) receiveJson(result,
|
|
34865
|
-
else receiveHuman(
|
|
35167
|
+
const output2 = await writeShareOutput(options.output ?? ".", result.metadata.display.filename ?? "share.md", result.bytes, options.force === true);
|
|
35168
|
+
if (json) receiveJson(result, output2);
|
|
35169
|
+
else receiveHuman(output2);
|
|
34866
35170
|
} catch (error) {
|
|
34867
35171
|
handleError(shareCliError(error));
|
|
34868
35172
|
}
|
|
@@ -34891,7 +35195,7 @@ function registerShareCommand(program) {
|
|
|
34891
35195
|
allowInsecureRegistry: options.insecureRegistry === true,
|
|
34892
35196
|
notify: options.notify === true,
|
|
34893
35197
|
targetAdapter: shareServices.targetAdapter,
|
|
34894
|
-
...publishServices()
|
|
35198
|
+
...publishServices(options.insecureRegistry === true)
|
|
34895
35199
|
});
|
|
34896
35200
|
if ("state" in result) throw new CLIError(result.method === "openkey-device" ? "DEVICE_AUTH_REQUIRED" : "CLAIM_REQUIRED", "recipient authorization is required; continue through the configured authority adapter", 6);
|
|
34897
35201
|
const record = await rememberPublishedShare(result);
|
|
@@ -34974,7 +35278,7 @@ init_profiles();
|
|
|
34974
35278
|
init_formatter();
|
|
34975
35279
|
init_errors();
|
|
34976
35280
|
init_constants();
|
|
34977
|
-
import { randomBytes as
|
|
35281
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
34978
35282
|
import { mkdir as mkdir4, writeFile as writeFile6 } from "fs/promises";
|
|
34979
35283
|
import { dirname as dirname3 } from "path";
|
|
34980
35284
|
init_host();
|
|
@@ -35057,7 +35361,7 @@ it directly with \`tc space host <name>\` (no request needed).
|
|
|
35057
35361
|
const artifact = {
|
|
35058
35362
|
kind: "tinycloud.host.request",
|
|
35059
35363
|
version: 1,
|
|
35060
|
-
requestId: `hostreq_${Date.now().toString(36)}_${
|
|
35364
|
+
requestId: `hostreq_${Date.now().toString(36)}_${randomBytes5(4).toString("hex")}`,
|
|
35061
35365
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35062
35366
|
spaceName,
|
|
35063
35367
|
spaceId,
|
|
@@ -36139,6 +36443,7 @@ function registerVarsCommand(program) {
|
|
|
36139
36443
|
function registerTinyCloudCommands(program) {
|
|
36140
36444
|
registerInitCommand(program);
|
|
36141
36445
|
registerAuthCommand(program);
|
|
36446
|
+
registerEnableCommand(program);
|
|
36142
36447
|
registerKvCommand(program);
|
|
36143
36448
|
registerSpaceCommand(program);
|
|
36144
36449
|
registerDelegationCommand(program);
|