@tinycloud/operations 0.5.2 → 0.5.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-name.cjs +32 -0
- package/dist/cli-name.cjs.map +1 -0
- package/dist/cli-name.d.cts +9 -0
- package/dist/cli-name.d.ts +9 -0
- package/dist/cli-name.js +7 -0
- package/dist/cli-name.js.map +1 -0
- package/dist/cli-runtime.cjs +84 -69
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.js +29 -14
- package/dist/cli-runtime.js.map +1 -1
- package/dist/delegation-binding.cjs +1 -1
- package/dist/delegation-binding.cjs.map +1 -1
- package/dist/delegation-binding.js +1 -1
- package/dist/delegation-binding.js.map +1 -1
- package/dist/index.cjs +84 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +29 -14
- package/dist/index.js.map +1 -1
- package/dist/recovery.cjs +11 -5
- package/dist/recovery.cjs.map +1 -1
- package/dist/recovery.js +9 -5
- package/dist/recovery.js.map +1 -1
- package/dist/secret-request.cjs +7 -1
- package/dist/secret-request.cjs.map +1 -1
- package/dist/secret-request.js +7 -1
- package/dist/secret-request.js.map +1 -1
- package/package.json +9 -4
package/dist/recovery.cjs
CHANGED
|
@@ -26,14 +26,20 @@ __export(recovery_exports, {
|
|
|
26
26
|
signInRecovery: () => signInRecovery
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(recovery_exports);
|
|
29
|
+
|
|
30
|
+
// src/cli-name.ts
|
|
31
|
+
var import_node_path = require("path");
|
|
32
|
+
var CLI_NAME = (0, import_node_path.basename)(process.argv[1] ?? "") === "tinycloud" ? "tinycloud" : "tc";
|
|
33
|
+
|
|
34
|
+
// src/recovery.ts
|
|
29
35
|
var SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
30
36
|
var MIGRATION_OPERATION_ID = "tinycloud.auth.capabilities";
|
|
31
37
|
var SIGN_IN_TEMPLATES = {
|
|
32
|
-
local: (profile) =>
|
|
33
|
-
openkey: (profile) =>
|
|
34
|
-
scoped: (profile) =>
|
|
38
|
+
local: (profile) => `${CLI_NAME} --profile ${profile} auth login --method local`,
|
|
39
|
+
openkey: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey`,
|
|
40
|
+
scoped: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey --paste --manifest <manifest.json>`
|
|
35
41
|
};
|
|
36
|
-
var migrationCommand = (profile) =>
|
|
42
|
+
var migrationCommand = (profile) => `${CLI_NAME} --profile ${profile} auth caps`;
|
|
37
43
|
function signInRecovery(profile, posture) {
|
|
38
44
|
if (!SAFE_PROFILE_TOKEN.test(profile)) return { action: "sign_in" };
|
|
39
45
|
const template = posture === "local-owner-key" ? SIGN_IN_TEMPLATES.local : posture === "delegate-session" ? SIGN_IN_TEMPLATES.scoped : SIGN_IN_TEMPLATES.openkey;
|
|
@@ -53,7 +59,7 @@ function isSafeRecovery(value, action) {
|
|
|
53
59
|
if (!("command" in record)) return true;
|
|
54
60
|
const command = record.command;
|
|
55
61
|
if (typeof command !== "string") return false;
|
|
56
|
-
const profile =
|
|
62
|
+
const profile = /^\S+ --profile (\S+) /.exec(command)?.[1];
|
|
57
63
|
if (profile === void 0 || !SAFE_PROFILE_TOKEN.test(profile)) return false;
|
|
58
64
|
return action === "sign_in" ? Object.values(SIGN_IN_TEMPLATES).some((template) => template(profile) === command) : migrationCommand(profile) === command;
|
|
59
65
|
}
|
package/dist/recovery.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/recovery.ts"],"sourcesContent":["import type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * What a caller does to recover from an operation error it cannot retry. The\n * single source of truth for recovery commands: operations attach it to error\n * details, and projections render hints from it.\n */\nexport type OperationRecovery =\n | { readonly action: \"sign_in\"; readonly command?: string }\n | {\n readonly action: \"complete_migration\";\n readonly command?: string;\n readonly operationId: typeof MIGRATION_OPERATION_ID;\n };\n\n/**\n * Profile names that may appear in a command. Profile names are any single\n * path segment, so a name outside this token gets a recovery without a\n * `command`: an agent never receives a command built from an unsafe string.\n */\nexport const SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;\n\n/** Runs the authenticated runtime on the profile's own restored session, which completes its migration. */\nconst MIGRATION_OPERATION_ID = \"tinycloud.auth.capabilities\";\n\nconst SIGN_IN_TEMPLATES: Readonly<Record<\"local\" | \"openkey\" | \"scoped\", (profile: string) => string>> = {\n local: (profile) =>
|
|
1
|
+
{"version":3,"sources":["../src/recovery.ts","../src/cli-name.ts"],"sourcesContent":["import { CLI_NAME } from \"./cli-name.js\";\nimport type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * What a caller does to recover from an operation error it cannot retry. The\n * single source of truth for recovery commands: operations attach it to error\n * details, and projections render hints from it.\n */\nexport type OperationRecovery =\n | { readonly action: \"sign_in\"; readonly command?: string }\n | {\n readonly action: \"complete_migration\";\n readonly command?: string;\n readonly operationId: typeof MIGRATION_OPERATION_ID;\n };\n\n/**\n * Profile names that may appear in a command. Profile names are any single\n * path segment, so a name outside this token gets a recovery without a\n * `command`: an agent never receives a command built from an unsafe string.\n */\nexport const SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;\n\n/** Runs the authenticated runtime on the profile's own restored session, which completes its migration. */\nconst MIGRATION_OPERATION_ID = \"tinycloud.auth.capabilities\";\n\nconst SIGN_IN_TEMPLATES: Readonly<Record<\"local\" | \"openkey\" | \"scoped\", (profile: string) => string>> = {\n local: (profile) => `${CLI_NAME} --profile ${profile} auth login --method local`,\n openkey: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey`,\n scoped: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey --paste --manifest <manifest.json>`,\n};\n\nconst migrationCommand = (profile: string): string => `${CLI_NAME} --profile ${profile} auth caps`;\n\n/**\n * The persistent login (one that writes a stored session) for a profile of\n * this posture. A delegate profile needs its owner to approve a scoped login.\n */\nexport function signInRecovery(profile: string, posture: TinyCloudPosture): OperationRecovery {\n if (!SAFE_PROFILE_TOKEN.test(profile)) return { action: \"sign_in\" };\n const template = posture === \"local-owner-key\"\n ? SIGN_IN_TEMPLATES.local\n : posture === \"delegate-session\"\n ? SIGN_IN_TEMPLATES.scoped\n : SIGN_IN_TEMPLATES.openkey;\n return { action: \"sign_in\", command: template(profile) };\n}\n\n/** One authenticated command on the profile's own session finishes its one-time binding migration. */\nexport function migrationRecovery(profile: string): OperationRecovery {\n return SAFE_PROFILE_TOKEN.test(profile)\n ? { action: \"complete_migration\", command: migrationCommand(profile), operationId: MIGRATION_OPERATION_ID }\n : { action: \"complete_migration\", operationId: MIGRATION_OPERATION_ID };\n}\n\n/**\n * Exact-shape check used by the error-detail redaction policies: exact keys, a\n * fixed `action`, and a `command` equal to one of the fixed templates for a\n * safe profile token.\n */\nexport function isSafeRecovery(\n value: unknown,\n action: OperationRecovery[\"action\"],\n): value is OperationRecovery {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const record = value as Record<string, unknown>;\n if (record.action !== action) return false;\n const keys = Object.keys(record);\n const allowed = action === \"sign_in\" ? [\"action\", \"command\"] : [\"action\", \"command\", \"operationId\"];\n if (!keys.every((key) => allowed.includes(key))) return false;\n if (action === \"complete_migration\" && record.operationId !== MIGRATION_OPERATION_ID) return false;\n if (!(\"command\" in record)) return true;\n const command = record.command;\n if (typeof command !== \"string\") return false;\n const profile = /^\\S+ --profile (\\S+) /.exec(command)?.[1];\n if (profile === undefined || !SAFE_PROFILE_TOKEN.test(profile)) return false;\n return action === \"sign_in\"\n ? Object.values(SIGN_IN_TEMPLATES).some((template) => template(profile) === command)\n : migrationCommand(profile) === command;\n}\n","import { basename } from \"node:path\";\n\n/**\n * The command name hints and messages tell users to run. The CLI installs as\n * both `tc` and `tinycloud` (Ubuntu's iproute2 ships `/usr/sbin/tc`), so the\n * name follows the invoked bin. Any other host process, such as the MCP server,\n * gets `tc`.\n */\nexport const CLI_NAME = basename(process.argv[1] ?? \"\") === \"tinycloud\" ? \"tinycloud\" : \"tc\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,uBAAyB;AAQlB,IAAM,eAAW,2BAAS,QAAQ,KAAK,CAAC,KAAK,EAAE,MAAM,cAAc,cAAc;;;ADajF,IAAM,qBAAqB;AAGlC,IAAM,yBAAyB;AAE/B,IAAM,oBAAmG;AAAA,EACvG,OAAO,CAAC,YAAY,GAAG,QAAQ,cAAc,OAAO;AAAA,EACpD,SAAS,CAAC,YAAY,GAAG,QAAQ,cAAc,OAAO;AAAA,EACtD,QAAQ,CAAC,YAAY,GAAG,QAAQ,cAAc,OAAO;AACvD;AAEA,IAAM,mBAAmB,CAAC,YAA4B,GAAG,QAAQ,cAAc,OAAO;AAM/E,SAAS,eAAe,SAAiB,SAA8C;AAC5F,MAAI,CAAC,mBAAmB,KAAK,OAAO,EAAG,QAAO,EAAE,QAAQ,UAAU;AAClE,QAAM,WAAW,YAAY,oBACzB,kBAAkB,QAClB,YAAY,qBACZ,kBAAkB,SAClB,kBAAkB;AACtB,SAAO,EAAE,QAAQ,WAAW,SAAS,SAAS,OAAO,EAAE;AACzD;AAGO,SAAS,kBAAkB,SAAoC;AACpE,SAAO,mBAAmB,KAAK,OAAO,IAClC,EAAE,QAAQ,sBAAsB,SAAS,iBAAiB,OAAO,GAAG,aAAa,uBAAuB,IACxG,EAAE,QAAQ,sBAAsB,aAAa,uBAAuB;AAC1E;AAOO,SAAS,eACd,OACA,QAC4B;AAC5B,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,EAAG,QAAO;AAChF,QAAM,SAAS;AACf,MAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,QAAM,OAAO,OAAO,KAAK,MAAM;AAC/B,QAAM,UAAU,WAAW,YAAY,CAAC,UAAU,SAAS,IAAI,CAAC,UAAU,WAAW,aAAa;AAClG,MAAI,CAAC,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,GAAG,CAAC,EAAG,QAAO;AACxD,MAAI,WAAW,wBAAwB,OAAO,gBAAgB,uBAAwB,QAAO;AAC7F,MAAI,EAAE,aAAa,QAAS,QAAO;AACnC,QAAM,UAAU,OAAO;AACvB,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,QAAM,UAAU,wBAAwB,KAAK,OAAO,IAAI,CAAC;AACzD,MAAI,YAAY,UAAa,CAAC,mBAAmB,KAAK,OAAO,EAAG,QAAO;AACvE,SAAO,WAAW,YACd,OAAO,OAAO,iBAAiB,EAAE,KAAK,CAAC,aAAa,SAAS,OAAO,MAAM,OAAO,IACjF,iBAAiB,OAAO,MAAM;AACpC;","names":[]}
|
package/dist/recovery.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
// src/cli-name.ts
|
|
2
|
+
import { basename } from "path";
|
|
3
|
+
var CLI_NAME = basename(process.argv[1] ?? "") === "tinycloud" ? "tinycloud" : "tc";
|
|
4
|
+
|
|
1
5
|
// src/recovery.ts
|
|
2
6
|
var SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
3
7
|
var MIGRATION_OPERATION_ID = "tinycloud.auth.capabilities";
|
|
4
8
|
var SIGN_IN_TEMPLATES = {
|
|
5
|
-
local: (profile) =>
|
|
6
|
-
openkey: (profile) =>
|
|
7
|
-
scoped: (profile) =>
|
|
9
|
+
local: (profile) => `${CLI_NAME} --profile ${profile} auth login --method local`,
|
|
10
|
+
openkey: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey`,
|
|
11
|
+
scoped: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey --paste --manifest <manifest.json>`
|
|
8
12
|
};
|
|
9
|
-
var migrationCommand = (profile) =>
|
|
13
|
+
var migrationCommand = (profile) => `${CLI_NAME} --profile ${profile} auth caps`;
|
|
10
14
|
function signInRecovery(profile, posture) {
|
|
11
15
|
if (!SAFE_PROFILE_TOKEN.test(profile)) return { action: "sign_in" };
|
|
12
16
|
const template = posture === "local-owner-key" ? SIGN_IN_TEMPLATES.local : posture === "delegate-session" ? SIGN_IN_TEMPLATES.scoped : SIGN_IN_TEMPLATES.openkey;
|
|
@@ -26,7 +30,7 @@ function isSafeRecovery(value, action) {
|
|
|
26
30
|
if (!("command" in record)) return true;
|
|
27
31
|
const command = record.command;
|
|
28
32
|
if (typeof command !== "string") return false;
|
|
29
|
-
const profile =
|
|
33
|
+
const profile = /^\S+ --profile (\S+) /.exec(command)?.[1];
|
|
30
34
|
if (profile === void 0 || !SAFE_PROFILE_TOKEN.test(profile)) return false;
|
|
31
35
|
return action === "sign_in" ? Object.values(SIGN_IN_TEMPLATES).some((template) => template(profile) === command) : migrationCommand(profile) === command;
|
|
32
36
|
}
|
package/dist/recovery.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/recovery.ts"],"sourcesContent":["import type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * What a caller does to recover from an operation error it cannot retry. The\n * single source of truth for recovery commands: operations attach it to error\n * details, and projections render hints from it.\n */\nexport type OperationRecovery =\n | { readonly action: \"sign_in\"; readonly command?: string }\n | {\n readonly action: \"complete_migration\";\n readonly command?: string;\n readonly operationId: typeof MIGRATION_OPERATION_ID;\n };\n\n/**\n * Profile names that may appear in a command. Profile names are any single\n * path segment, so a name outside this token gets a recovery without a\n * `command`: an agent never receives a command built from an unsafe string.\n */\nexport const SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;\n\n/** Runs the authenticated runtime on the profile's own restored session, which completes its migration. */\nconst MIGRATION_OPERATION_ID = \"tinycloud.auth.capabilities\";\n\nconst SIGN_IN_TEMPLATES: Readonly<Record<\"local\" | \"openkey\" | \"scoped\", (profile: string) => string>> = {\n local: (profile) =>
|
|
1
|
+
{"version":3,"sources":["../src/cli-name.ts","../src/recovery.ts"],"sourcesContent":["import { basename } from \"node:path\";\n\n/**\n * The command name hints and messages tell users to run. The CLI installs as\n * both `tc` and `tinycloud` (Ubuntu's iproute2 ships `/usr/sbin/tc`), so the\n * name follows the invoked bin. Any other host process, such as the MCP server,\n * gets `tc`.\n */\nexport const CLI_NAME = basename(process.argv[1] ?? \"\") === \"tinycloud\" ? \"tinycloud\" : \"tc\";\n","import { CLI_NAME } from \"./cli-name.js\";\nimport type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * What a caller does to recover from an operation error it cannot retry. The\n * single source of truth for recovery commands: operations attach it to error\n * details, and projections render hints from it.\n */\nexport type OperationRecovery =\n | { readonly action: \"sign_in\"; readonly command?: string }\n | {\n readonly action: \"complete_migration\";\n readonly command?: string;\n readonly operationId: typeof MIGRATION_OPERATION_ID;\n };\n\n/**\n * Profile names that may appear in a command. Profile names are any single\n * path segment, so a name outside this token gets a recovery without a\n * `command`: an agent never receives a command built from an unsafe string.\n */\nexport const SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;\n\n/** Runs the authenticated runtime on the profile's own restored session, which completes its migration. */\nconst MIGRATION_OPERATION_ID = \"tinycloud.auth.capabilities\";\n\nconst SIGN_IN_TEMPLATES: Readonly<Record<\"local\" | \"openkey\" | \"scoped\", (profile: string) => string>> = {\n local: (profile) => `${CLI_NAME} --profile ${profile} auth login --method local`,\n openkey: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey`,\n scoped: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey --paste --manifest <manifest.json>`,\n};\n\nconst migrationCommand = (profile: string): string => `${CLI_NAME} --profile ${profile} auth caps`;\n\n/**\n * The persistent login (one that writes a stored session) for a profile of\n * this posture. A delegate profile needs its owner to approve a scoped login.\n */\nexport function signInRecovery(profile: string, posture: TinyCloudPosture): OperationRecovery {\n if (!SAFE_PROFILE_TOKEN.test(profile)) return { action: \"sign_in\" };\n const template = posture === \"local-owner-key\"\n ? SIGN_IN_TEMPLATES.local\n : posture === \"delegate-session\"\n ? SIGN_IN_TEMPLATES.scoped\n : SIGN_IN_TEMPLATES.openkey;\n return { action: \"sign_in\", command: template(profile) };\n}\n\n/** One authenticated command on the profile's own session finishes its one-time binding migration. */\nexport function migrationRecovery(profile: string): OperationRecovery {\n return SAFE_PROFILE_TOKEN.test(profile)\n ? { action: \"complete_migration\", command: migrationCommand(profile), operationId: MIGRATION_OPERATION_ID }\n : { action: \"complete_migration\", operationId: MIGRATION_OPERATION_ID };\n}\n\n/**\n * Exact-shape check used by the error-detail redaction policies: exact keys, a\n * fixed `action`, and a `command` equal to one of the fixed templates for a\n * safe profile token.\n */\nexport function isSafeRecovery(\n value: unknown,\n action: OperationRecovery[\"action\"],\n): value is OperationRecovery {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const record = value as Record<string, unknown>;\n if (record.action !== action) return false;\n const keys = Object.keys(record);\n const allowed = action === \"sign_in\" ? [\"action\", \"command\"] : [\"action\", \"command\", \"operationId\"];\n if (!keys.every((key) => allowed.includes(key))) return false;\n if (action === \"complete_migration\" && record.operationId !== MIGRATION_OPERATION_ID) return false;\n if (!(\"command\" in record)) return true;\n const command = record.command;\n if (typeof command !== \"string\") return false;\n const profile = /^\\S+ --profile (\\S+) /.exec(command)?.[1];\n if (profile === undefined || !SAFE_PROFILE_TOKEN.test(profile)) return false;\n return action === \"sign_in\"\n ? Object.values(SIGN_IN_TEMPLATES).some((template) => template(profile) === command)\n : migrationCommand(profile) === command;\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AAQlB,IAAM,WAAW,SAAS,QAAQ,KAAK,CAAC,KAAK,EAAE,MAAM,cAAc,cAAc;;;ACajF,IAAM,qBAAqB;AAGlC,IAAM,yBAAyB;AAE/B,IAAM,oBAAmG;AAAA,EACvG,OAAO,CAAC,YAAY,GAAG,QAAQ,cAAc,OAAO;AAAA,EACpD,SAAS,CAAC,YAAY,GAAG,QAAQ,cAAc,OAAO;AAAA,EACtD,QAAQ,CAAC,YAAY,GAAG,QAAQ,cAAc,OAAO;AACvD;AAEA,IAAM,mBAAmB,CAAC,YAA4B,GAAG,QAAQ,cAAc,OAAO;AAM/E,SAAS,eAAe,SAAiB,SAA8C;AAC5F,MAAI,CAAC,mBAAmB,KAAK,OAAO,EAAG,QAAO,EAAE,QAAQ,UAAU;AAClE,QAAM,WAAW,YAAY,oBACzB,kBAAkB,QAClB,YAAY,qBACZ,kBAAkB,SAClB,kBAAkB;AACtB,SAAO,EAAE,QAAQ,WAAW,SAAS,SAAS,OAAO,EAAE;AACzD;AAGO,SAAS,kBAAkB,SAAoC;AACpE,SAAO,mBAAmB,KAAK,OAAO,IAClC,EAAE,QAAQ,sBAAsB,SAAS,iBAAiB,OAAO,GAAG,aAAa,uBAAuB,IACxG,EAAE,QAAQ,sBAAsB,aAAa,uBAAuB;AAC1E;AAOO,SAAS,eACd,OACA,QAC4B;AAC5B,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,EAAG,QAAO;AAChF,QAAM,SAAS;AACf,MAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,QAAM,OAAO,OAAO,KAAK,MAAM;AAC/B,QAAM,UAAU,WAAW,YAAY,CAAC,UAAU,SAAS,IAAI,CAAC,UAAU,WAAW,aAAa;AAClG,MAAI,CAAC,KAAK,MAAM,CAAC,QAAQ,QAAQ,SAAS,GAAG,CAAC,EAAG,QAAO;AACxD,MAAI,WAAW,wBAAwB,OAAO,gBAAgB,uBAAwB,QAAO;AAC7F,MAAI,EAAE,aAAa,QAAS,QAAO;AACnC,QAAM,UAAU,OAAO;AACvB,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,QAAM,UAAU,wBAAwB,KAAK,OAAO,IAAI,CAAC;AACzD,MAAI,YAAY,UAAa,CAAC,mBAAmB,KAAK,OAAO,EAAG,QAAO;AACvE,SAAO,WAAW,YACd,OAAO,OAAO,iBAAiB,EAAE,KAAK,CAAC,aAAa,SAAS,OAAO,MAAM,OAAO,IACjF,iBAAiB,OAAO,MAAM;AACpC;","names":[]}
|
package/dist/secret-request.cjs
CHANGED
|
@@ -48,6 +48,12 @@ __export(secret_request_exports, {
|
|
|
48
48
|
});
|
|
49
49
|
module.exports = __toCommonJS(secret_request_exports);
|
|
50
50
|
var nodeSdk = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
51
|
+
|
|
52
|
+
// src/cli-name.ts
|
|
53
|
+
var import_node_path = require("path");
|
|
54
|
+
var CLI_NAME = (0, import_node_path.basename)(process.argv[1] ?? "") === "tinycloud" ? "tinycloud" : "tc";
|
|
55
|
+
|
|
56
|
+
// src/secret-request.ts
|
|
51
57
|
var SECRET_MANAGER_ORIGIN = "https://secrets.tinycloud.xyz";
|
|
52
58
|
var SECRET_REQUEST_MAX_NAMES = 10;
|
|
53
59
|
var SECRET_REQUEST_MAX_URL_LENGTH = 8192;
|
|
@@ -242,7 +248,7 @@ function ownerOfSpace(space) {
|
|
|
242
248
|
return ownerOrUndefined(`did:pkh:eip155:${match[1]}:${match[2].toLowerCase()}`);
|
|
243
249
|
}
|
|
244
250
|
function putCommands(names, scope) {
|
|
245
|
-
return dedupe(names).map((name) =>
|
|
251
|
+
return dedupe(names).map((name) => `${CLI_NAME} secrets put ${name}${scope === void 0 ? "" : ` --scope ${scope}`} --stdin`).join("; ");
|
|
246
252
|
}
|
|
247
253
|
function dedupe(names) {
|
|
248
254
|
return [...new Set(names)];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/secret-request.ts"],"sourcesContent":["// Namespace-shaped so modules that bundle this one stay compatible with\n// lightweight node-sdk test doubles.\nimport * as nodeSdk from \"@tinycloud/node-sdk\";\n\nimport type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * The one builder for Secret Manager links (TC-832 \"Link\"). Every\n * missing-secret path produces its `/request` link here, and `/app` links come\n * only from {@link buildSecretManagerUrl}. No parameter ever carries a value.\n */\nexport const SECRET_MANAGER_ORIGIN = \"https://secrets.tinycloud.xyz\";\nexport const SECRET_REQUEST_MAX_NAMES = 10;\nexport const SECRET_REQUEST_MAX_URL_LENGTH = 8192;\n\nconst OWNER_RE = /^did:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40})$/;\nconst NAME_RE = /^[A-Z][A-Z0-9_]*$/;\nconst MAX_NAME_LENGTH = 64;\nconst SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst MAX_SCOPE_LENGTH = 64;\nconst RESERVED_SCOPES: readonly string[] = [\"default\", \"global\", \"scoped\"];\nconst MAX_REQUESTER_LENGTH = 64;\nconst CONTROL_OR_FORMAT = /[\\p{Cc}\\p{Cf}]/u;\nconst STATE_RE = /^[A-Za-z0-9._~-]{1,128}$/;\nconst MAX_RETURN_TO_LENGTH = 2048;\nconst RETURN_TO_ORIGINS: readonly string[] = [\"https://feed.tinycloud.xyz\"];\nconst RETURN_TO_PREVIEW = /^https:\\/\\/[a-z0-9-]+\\.tinyfeed\\.pages\\.dev$/;\nconst PKH_SPACE_RE = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40}):[^:]+$/;\n\nexport type SecretRequestInputErrorCode =\n | \"INVALID_SECRET_NAME\"\n | \"TOO_MANY_SECRET_NAMES\"\n | \"INVALID_SECRET_SCOPE\"\n | \"INVALID_OWNER\"\n | \"INVALID_REQUESTER\"\n | \"INVALID_RETURN_TO\"\n | \"INVALID_STATE\"\n | \"REQUEST_LINK_TOO_LONG\";\n\nexport class SecretRequestInputError extends Error {\n readonly code: SecretRequestInputErrorCode;\n\n constructor(code: SecretRequestInputErrorCode, message: string) {\n super(message);\n this.name = \"SecretRequestInputError\";\n this.code = code;\n }\n}\n\n/**\n * TC-832 owner rule: `did:pkh:eip155:<chain>:<address>` with a safe-integer\n * chain and a lowercase `0x`. An address with any uppercase letter must\n * already be its EIP-55 form. Returns the DID with the EIP-55 address.\n */\nexport function canonicalSecretRequestOwner(did: string): string {\n const match = typeof did === \"string\" ? OWNER_RE.exec(did) : null;\n if (match === null || !Number.isSafeInteger(Number(match[1]))) throw invalidOwner();\n const address = match[2]!;\n let checksummed: string;\n try {\n checksummed = nodeSdk.canonicalizeAddress(address);\n } catch {\n throw invalidOwner();\n }\n if (/[A-F]/.test(address.slice(2)) && address !== checksummed) throw invalidOwner();\n return `did:pkh:eip155:${match[1]}:${checksummed}`;\n}\n\nexport interface SecretRequestLinkInput {\n readonly owner: string;\n readonly names: readonly string[];\n readonly scope?: string;\n readonly requester?: string;\n readonly returnTo?: string;\n readonly state?: string;\n}\n\nexport interface SecretRequestLink {\n readonly url: string;\n readonly owner: string;\n readonly names: string[];\n readonly scope?: string;\n}\n\n/** Builds the canonical `/request` link, or throws {@link SecretRequestInputError}. */\nexport function buildSecretRequestUrl(input: SecretRequestLinkInput): SecretRequestLink {\n const owner = canonicalSecretRequestOwner(input.owner);\n const names = validSecretNames(input.names);\n if (input.scope !== undefined) assertSecretRequestScope(input.scope);\n const requester = input.requester;\n if (\n requester !== undefined &&\n (typeof requester !== \"string\" || requester.length === 0 || requester.length > MAX_REQUESTER_LENGTH ||\n CONTROL_OR_FORMAT.test(requester))\n ) {\n throw new SecretRequestInputError(\"INVALID_REQUESTER\", \"The requester label must be 1-64 characters with no control or format characters.\");\n }\n if (input.returnTo !== undefined && !isAllowedReturnTo(input.returnTo)) {\n throw new SecretRequestInputError(\"INVALID_RETURN_TO\", \"The return address is not an allowed Secret Manager callback.\");\n }\n if (input.state !== undefined && !STATE_RE.test(input.state)) {\n throw new SecretRequestInputError(\"INVALID_STATE\", \"The state must be 1-128 characters of A-Z, a-z, 0-9, '.', '_', '~' or '-'.\");\n }\n\n const parameters: [string, string][] = [\n [\"owner\", owner],\n ...names.map((name): [string, string] => [\"name\", name]),\n ...(input.scope === undefined ? [] : [[\"scope\", input.scope] as [string, string]]),\n ...(input.requester === undefined ? [] : [[\"requester\", input.requester] as [string, string]]),\n ...(input.returnTo === undefined ? [] : [[\"return_to\", input.returnTo] as [string, string]]),\n ...(input.state === undefined ? [] : [[\"state\", input.state] as [string, string]]),\n [\"v\", \"1\"],\n ];\n const url = `${SECRET_MANAGER_ORIGIN}/request?${parameters\n .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)\n .join(\"&\")}`;\n if (url.length > SECRET_REQUEST_MAX_URL_LENGTH) {\n throw new SecretRequestInputError(\"REQUEST_LINK_TOO_LONG\", `The request link is longer than ${SECRET_REQUEST_MAX_URL_LENGTH} characters.`);\n }\n return { url, owner, names, ...(input.scope === undefined ? {} : { scope: input.scope }) };\n}\n\n/** The value-free setup a missing secret reports: exactly these keys. */\nexport interface SecretSetup {\n readonly url: string;\n readonly names: string[];\n readonly scope?: string;\n readonly owner: string;\n readonly action: \"add_secret\";\n}\n\nexport function secretSetupFor(link: SecretRequestLink): SecretSetup {\n return {\n url: link.url,\n names: [...link.names],\n ...(link.scope === undefined ? {} : { scope: link.scope }),\n owner: link.owner,\n action: \"add_secret\",\n };\n}\n\n/**\n * The full manager (`/app`), the only path that replaces an existing value:\n * bare, `?secret=N`, or `?secrets=A,B`, plus `&scope=`. Names follow the link\n * name rule without the request cap.\n */\nexport function buildSecretManagerUrl(input: { readonly names?: readonly string[]; readonly scope?: string } = {}): string {\n const names = dedupe(input.names ?? []);\n for (const name of names) assertSecretName(name);\n if (input.scope !== undefined) assertSecretRequestScope(input.scope);\n const parameters: [string, string][] = [\n ...(names.length === 1 ? [[\"secret\", names[0]!] as [string, string]] : []),\n ...(names.length > 1 ? [[\"secrets\", names.join(\",\")] as [string, string]] : []),\n ...(input.scope === undefined ? [] : [[\"scope\", input.scope] as [string, string]]),\n ];\n const query = parameters.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(\"&\");\n return `${SECRET_MANAGER_ORIGIN}/app${query === \"\" ? \"\" : `?${query}`}`;\n}\n\nexport type SecretsOwnerResolution =\n | { readonly ok: true; readonly owner: string; readonly local: boolean }\n | { readonly ok: false; readonly code: \"OWNER_DID_UNKNOWN\" | \"OWNER_MISMATCH\"; readonly message: string };\n\n/**\n * The owner whose secrets an identity reads, first match wins: a local key's\n * own account, the recorded owner, a delegate session's space owner, a pkh\n * principal, then the requested owner. A requested owner that disagrees with a\n * resolved one is `OWNER_MISMATCH`.\n */\nexport function resolveSecretsOwner(\n profile: {\n readonly local: boolean;\n readonly posture: TinyCloudPosture;\n readonly ownerDid?: string;\n readonly principalDid?: string;\n readonly space?: string;\n },\n requestedOwner?: string,\n): SecretsOwnerResolution {\n const resolved = profile.local\n ? ownerOrUndefined(profile.principalDid)\n : ownerOrUndefined(profile.ownerDid) ??\n (profile.posture === \"delegate-session\" ? ownerOfSpace(profile.space) : undefined) ??\n ownerOrUndefined(profile.principalDid);\n const requested = requestedOwner === undefined ? undefined : ownerOrUndefined(requestedOwner);\n\n if (resolved !== undefined) {\n if (requestedOwner !== undefined && requested !== resolved) {\n return {\n ok: false,\n code: \"OWNER_MISMATCH\",\n message: \"The requested owner is not the owner of this identity's secrets.\",\n };\n }\n return { ok: true, owner: resolved, local: profile.local };\n }\n if (requested !== undefined && !profile.local) return { ok: true, owner: requested, local: false };\n return {\n ok: false,\n code: \"OWNER_DID_UNKNOWN\",\n message: \"The owner of this identity's secrets is unknown. Pass the owner's did:pkh with --owner.\",\n };\n}\n\nexport type MissingSecretRecovery =\n | { readonly kind: \"add_secret\"; readonly setup: SecretSetup }\n | { readonly kind: \"put_locally\"; readonly hint: string }\n | { readonly kind: \"not_requestable\"; readonly hint: string };\n\n/**\n * How a caller gets missing secrets: a local identity stores them itself; any\n * other identity gets a `/request` link when one can express the request.\n * Never throws.\n */\nexport function missingSecretRecovery(input: {\n readonly owner: string | undefined;\n readonly local: boolean;\n readonly names: readonly string[];\n readonly scope?: string;\n}): MissingSecretRecovery {\n if (input.local) {\n return { kind: \"put_locally\", hint: `Store it with: ${putCommands(input.names, input.scope)}` };\n }\n if (input.owner !== undefined) {\n try {\n return {\n kind: \"add_secret\",\n setup: secretSetupFor(buildSecretRequestUrl({\n owner: input.owner,\n names: input.names,\n ...(input.scope === undefined ? {} : { scope: input.scope }),\n })),\n };\n } catch {\n // A legacy name or scope, or an unusable owner, cannot be requested.\n }\n }\n return {\n kind: \"not_requestable\",\n hint: `No Secret Manager request link can express this secret. The owner can store it with: ${putCommands(input.names, input.scope)}`,\n };\n}\n\n/**\n * The strict policy for a consent denial's `request_access` recovery: exact\n * keys, denied names only, a canonical owner and a valid scope. Any deviation\n * rejects the whole value.\n */\nexport function isSafeRequestAccessRecovery(\n value: unknown,\n deniedNames: readonly string[],\n): value is { action: \"request_access\"; names: string[]; owner: string; scope?: string } {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record);\n if (!keys.every((key) => key === \"action\" || key === \"names\" || key === \"owner\" || key === \"scope\")) return false;\n if (record.action !== \"request_access\") return false;\n const names = record.names;\n if (\n !Array.isArray(names) || names.length === 0 || names.length > SECRET_REQUEST_MAX_NAMES ||\n new Set(names).size !== names.length ||\n !names.every((name) => typeof name === \"string\" && isValidSecretName(name) && deniedNames.includes(name))\n ) {\n return false;\n }\n if (typeof record.owner !== \"string\" || ownerOrUndefined(record.owner) !== record.owner) return false;\n if (\"scope\" in record && (typeof record.scope !== \"string\" || !isValidSecretRequestScope(record.scope))) return false;\n return true;\n}\n\n/** TC-832 scope rule: canonical grammar, at most 64, not reserved. Never canonicalizes. */\nexport function isValidSecretRequestScope(scope: string): boolean {\n return scope.length <= MAX_SCOPE_LENGTH && SCOPE_RE.test(scope) && !RESERVED_SCOPES.includes(scope);\n}\n\n/** TC-832 name rule: `^[A-Z][A-Z0-9_]*$`, at most 64, no trimming. */\nexport function isValidSecretName(name: string): boolean {\n return name.length <= MAX_NAME_LENGTH && NAME_RE.test(name);\n}\n\n/** Validated, de-duplicated (order kept) names, 1-10 of them. */\nexport function validSecretNames(names: readonly string[]): string[] {\n for (const name of names) assertSecretName(name);\n const unique = dedupe(names);\n if (unique.length === 0) {\n throw new SecretRequestInputError(\"INVALID_SECRET_NAME\", \"At least one secret name is required.\");\n }\n if (unique.length > SECRET_REQUEST_MAX_NAMES) {\n throw new SecretRequestInputError(\"TOO_MANY_SECRET_NAMES\", `At most ${SECRET_REQUEST_MAX_NAMES} secret names can be requested at once.`);\n }\n return unique;\n}\n\nfunction assertSecretName(name: string): void {\n if (typeof name !== \"string\" || !isValidSecretName(name)) {\n throw new SecretRequestInputError(\"INVALID_SECRET_NAME\", \"Secret names are 1-64 characters of A-Z, 0-9 and '_', starting with a letter.\");\n }\n}\n\nfunction assertSecretRequestScope(scope: string): void {\n if (typeof scope !== \"string\" || !isValidSecretRequestScope(scope)) {\n throw new SecretRequestInputError(\"INVALID_SECRET_SCOPE\", \"The scope must be a canonical scope of at most 64 characters, not default, global or scoped.\");\n }\n}\n\nfunction isAllowedReturnTo(returnTo: string): boolean {\n if (typeof returnTo !== \"string\" || returnTo.length > MAX_RETURN_TO_LENGTH) return false;\n let url: URL;\n try {\n url = new URL(returnTo);\n } catch {\n return false;\n }\n if (url.username !== \"\" || url.password !== \"\") return false;\n return RETURN_TO_ORIGINS.includes(url.origin) || RETURN_TO_PREVIEW.test(url.origin);\n}\n\nfunction ownerOrUndefined(did: string | undefined): string | undefined {\n if (did === undefined) return undefined;\n try {\n return canonicalSecretRequestOwner(did);\n } catch {\n return undefined;\n }\n}\n\n/** The canonical owner DID of a `tinycloud:pkh:eip155:…` space, or `undefined` for any other space. */\nexport function ownerOfSpace(space: string | undefined): string | undefined {\n const match = space === undefined ? null : PKH_SPACE_RE.exec(space);\n if (match === null) return undefined;\n // Space URIs carry the address in any case; the owner is its EIP-55 form.\n return ownerOrUndefined(`did:pkh:eip155:${match[1]}:${match[2]!.toLowerCase()}`);\n}\n\nfunction putCommands(names: readonly string[], scope: string | undefined): string {\n return dedupe(names)\n .map((name) => `tc secrets put ${name}${scope === undefined ? \"\" : ` --scope ${scope}`} --stdin`)\n .join(\"; \");\n}\n\nfunction dedupe(names: readonly string[]): string[] {\n return [...new Set(names)];\n}\n\nfunction invalidOwner(): SecretRequestInputError {\n return new SecretRequestInputError(\"INVALID_OWNER\", \"The owner must be a did:pkh:eip155 account with a valid EIP-55 address.\");\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/secret-request.ts","../src/cli-name.ts"],"sourcesContent":["// Namespace-shaped so modules that bundle this one stay compatible with\n// lightweight node-sdk test doubles.\nimport * as nodeSdk from \"@tinycloud/node-sdk\";\n\nimport { CLI_NAME } from \"./cli-name.js\";\nimport type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * The one builder for Secret Manager links (TC-832 \"Link\"). Every\n * missing-secret path produces its `/request` link here, and `/app` links come\n * only from {@link buildSecretManagerUrl}. No parameter ever carries a value.\n */\nexport const SECRET_MANAGER_ORIGIN = \"https://secrets.tinycloud.xyz\";\nexport const SECRET_REQUEST_MAX_NAMES = 10;\nexport const SECRET_REQUEST_MAX_URL_LENGTH = 8192;\n\nconst OWNER_RE = /^did:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40})$/;\nconst NAME_RE = /^[A-Z][A-Z0-9_]*$/;\nconst MAX_NAME_LENGTH = 64;\nconst SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst MAX_SCOPE_LENGTH = 64;\nconst RESERVED_SCOPES: readonly string[] = [\"default\", \"global\", \"scoped\"];\nconst MAX_REQUESTER_LENGTH = 64;\nconst CONTROL_OR_FORMAT = /[\\p{Cc}\\p{Cf}]/u;\nconst STATE_RE = /^[A-Za-z0-9._~-]{1,128}$/;\nconst MAX_RETURN_TO_LENGTH = 2048;\nconst RETURN_TO_ORIGINS: readonly string[] = [\"https://feed.tinycloud.xyz\"];\nconst RETURN_TO_PREVIEW = /^https:\\/\\/[a-z0-9-]+\\.tinyfeed\\.pages\\.dev$/;\nconst PKH_SPACE_RE = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40}):[^:]+$/;\n\nexport type SecretRequestInputErrorCode =\n | \"INVALID_SECRET_NAME\"\n | \"TOO_MANY_SECRET_NAMES\"\n | \"INVALID_SECRET_SCOPE\"\n | \"INVALID_OWNER\"\n | \"INVALID_REQUESTER\"\n | \"INVALID_RETURN_TO\"\n | \"INVALID_STATE\"\n | \"REQUEST_LINK_TOO_LONG\";\n\nexport class SecretRequestInputError extends Error {\n readonly code: SecretRequestInputErrorCode;\n\n constructor(code: SecretRequestInputErrorCode, message: string) {\n super(message);\n this.name = \"SecretRequestInputError\";\n this.code = code;\n }\n}\n\n/**\n * TC-832 owner rule: `did:pkh:eip155:<chain>:<address>` with a safe-integer\n * chain and a lowercase `0x`. An address with any uppercase letter must\n * already be its EIP-55 form. Returns the DID with the EIP-55 address.\n */\nexport function canonicalSecretRequestOwner(did: string): string {\n const match = typeof did === \"string\" ? OWNER_RE.exec(did) : null;\n if (match === null || !Number.isSafeInteger(Number(match[1]))) throw invalidOwner();\n const address = match[2]!;\n let checksummed: string;\n try {\n checksummed = nodeSdk.canonicalizeAddress(address);\n } catch {\n throw invalidOwner();\n }\n if (/[A-F]/.test(address.slice(2)) && address !== checksummed) throw invalidOwner();\n return `did:pkh:eip155:${match[1]}:${checksummed}`;\n}\n\nexport interface SecretRequestLinkInput {\n readonly owner: string;\n readonly names: readonly string[];\n readonly scope?: string;\n readonly requester?: string;\n readonly returnTo?: string;\n readonly state?: string;\n}\n\nexport interface SecretRequestLink {\n readonly url: string;\n readonly owner: string;\n readonly names: string[];\n readonly scope?: string;\n}\n\n/** Builds the canonical `/request` link, or throws {@link SecretRequestInputError}. */\nexport function buildSecretRequestUrl(input: SecretRequestLinkInput): SecretRequestLink {\n const owner = canonicalSecretRequestOwner(input.owner);\n const names = validSecretNames(input.names);\n if (input.scope !== undefined) assertSecretRequestScope(input.scope);\n const requester = input.requester;\n if (\n requester !== undefined &&\n (typeof requester !== \"string\" || requester.length === 0 || requester.length > MAX_REQUESTER_LENGTH ||\n CONTROL_OR_FORMAT.test(requester))\n ) {\n throw new SecretRequestInputError(\"INVALID_REQUESTER\", \"The requester label must be 1-64 characters with no control or format characters.\");\n }\n if (input.returnTo !== undefined && !isAllowedReturnTo(input.returnTo)) {\n throw new SecretRequestInputError(\"INVALID_RETURN_TO\", \"The return address is not an allowed Secret Manager callback.\");\n }\n if (input.state !== undefined && !STATE_RE.test(input.state)) {\n throw new SecretRequestInputError(\"INVALID_STATE\", \"The state must be 1-128 characters of A-Z, a-z, 0-9, '.', '_', '~' or '-'.\");\n }\n\n const parameters: [string, string][] = [\n [\"owner\", owner],\n ...names.map((name): [string, string] => [\"name\", name]),\n ...(input.scope === undefined ? [] : [[\"scope\", input.scope] as [string, string]]),\n ...(input.requester === undefined ? [] : [[\"requester\", input.requester] as [string, string]]),\n ...(input.returnTo === undefined ? [] : [[\"return_to\", input.returnTo] as [string, string]]),\n ...(input.state === undefined ? [] : [[\"state\", input.state] as [string, string]]),\n [\"v\", \"1\"],\n ];\n const url = `${SECRET_MANAGER_ORIGIN}/request?${parameters\n .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)\n .join(\"&\")}`;\n if (url.length > SECRET_REQUEST_MAX_URL_LENGTH) {\n throw new SecretRequestInputError(\"REQUEST_LINK_TOO_LONG\", `The request link is longer than ${SECRET_REQUEST_MAX_URL_LENGTH} characters.`);\n }\n return { url, owner, names, ...(input.scope === undefined ? {} : { scope: input.scope }) };\n}\n\n/** The value-free setup a missing secret reports: exactly these keys. */\nexport interface SecretSetup {\n readonly url: string;\n readonly names: string[];\n readonly scope?: string;\n readonly owner: string;\n readonly action: \"add_secret\";\n}\n\nexport function secretSetupFor(link: SecretRequestLink): SecretSetup {\n return {\n url: link.url,\n names: [...link.names],\n ...(link.scope === undefined ? {} : { scope: link.scope }),\n owner: link.owner,\n action: \"add_secret\",\n };\n}\n\n/**\n * The full manager (`/app`), the only path that replaces an existing value:\n * bare, `?secret=N`, or `?secrets=A,B`, plus `&scope=`. Names follow the link\n * name rule without the request cap.\n */\nexport function buildSecretManagerUrl(input: { readonly names?: readonly string[]; readonly scope?: string } = {}): string {\n const names = dedupe(input.names ?? []);\n for (const name of names) assertSecretName(name);\n if (input.scope !== undefined) assertSecretRequestScope(input.scope);\n const parameters: [string, string][] = [\n ...(names.length === 1 ? [[\"secret\", names[0]!] as [string, string]] : []),\n ...(names.length > 1 ? [[\"secrets\", names.join(\",\")] as [string, string]] : []),\n ...(input.scope === undefined ? [] : [[\"scope\", input.scope] as [string, string]]),\n ];\n const query = parameters.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(\"&\");\n return `${SECRET_MANAGER_ORIGIN}/app${query === \"\" ? \"\" : `?${query}`}`;\n}\n\nexport type SecretsOwnerResolution =\n | { readonly ok: true; readonly owner: string; readonly local: boolean }\n | { readonly ok: false; readonly code: \"OWNER_DID_UNKNOWN\" | \"OWNER_MISMATCH\"; readonly message: string };\n\n/**\n * The owner whose secrets an identity reads, first match wins: a local key's\n * own account, the recorded owner, a delegate session's space owner, a pkh\n * principal, then the requested owner. A requested owner that disagrees with a\n * resolved one is `OWNER_MISMATCH`.\n */\nexport function resolveSecretsOwner(\n profile: {\n readonly local: boolean;\n readonly posture: TinyCloudPosture;\n readonly ownerDid?: string;\n readonly principalDid?: string;\n readonly space?: string;\n },\n requestedOwner?: string,\n): SecretsOwnerResolution {\n const resolved = profile.local\n ? ownerOrUndefined(profile.principalDid)\n : ownerOrUndefined(profile.ownerDid) ??\n (profile.posture === \"delegate-session\" ? ownerOfSpace(profile.space) : undefined) ??\n ownerOrUndefined(profile.principalDid);\n const requested = requestedOwner === undefined ? undefined : ownerOrUndefined(requestedOwner);\n\n if (resolved !== undefined) {\n if (requestedOwner !== undefined && requested !== resolved) {\n return {\n ok: false,\n code: \"OWNER_MISMATCH\",\n message: \"The requested owner is not the owner of this identity's secrets.\",\n };\n }\n return { ok: true, owner: resolved, local: profile.local };\n }\n if (requested !== undefined && !profile.local) return { ok: true, owner: requested, local: false };\n return {\n ok: false,\n code: \"OWNER_DID_UNKNOWN\",\n message: \"The owner of this identity's secrets is unknown. Pass the owner's did:pkh with --owner.\",\n };\n}\n\nexport type MissingSecretRecovery =\n | { readonly kind: \"add_secret\"; readonly setup: SecretSetup }\n | { readonly kind: \"put_locally\"; readonly hint: string }\n | { readonly kind: \"not_requestable\"; readonly hint: string };\n\n/**\n * How a caller gets missing secrets: a local identity stores them itself; any\n * other identity gets a `/request` link when one can express the request.\n * Never throws.\n */\nexport function missingSecretRecovery(input: {\n readonly owner: string | undefined;\n readonly local: boolean;\n readonly names: readonly string[];\n readonly scope?: string;\n}): MissingSecretRecovery {\n if (input.local) {\n return { kind: \"put_locally\", hint: `Store it with: ${putCommands(input.names, input.scope)}` };\n }\n if (input.owner !== undefined) {\n try {\n return {\n kind: \"add_secret\",\n setup: secretSetupFor(buildSecretRequestUrl({\n owner: input.owner,\n names: input.names,\n ...(input.scope === undefined ? {} : { scope: input.scope }),\n })),\n };\n } catch {\n // A legacy name or scope, or an unusable owner, cannot be requested.\n }\n }\n return {\n kind: \"not_requestable\",\n hint: `No Secret Manager request link can express this secret. The owner can store it with: ${putCommands(input.names, input.scope)}`,\n };\n}\n\n/**\n * The strict policy for a consent denial's `request_access` recovery: exact\n * keys, denied names only, a canonical owner and a valid scope. Any deviation\n * rejects the whole value.\n */\nexport function isSafeRequestAccessRecovery(\n value: unknown,\n deniedNames: readonly string[],\n): value is { action: \"request_access\"; names: string[]; owner: string; scope?: string } {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record);\n if (!keys.every((key) => key === \"action\" || key === \"names\" || key === \"owner\" || key === \"scope\")) return false;\n if (record.action !== \"request_access\") return false;\n const names = record.names;\n if (\n !Array.isArray(names) || names.length === 0 || names.length > SECRET_REQUEST_MAX_NAMES ||\n new Set(names).size !== names.length ||\n !names.every((name) => typeof name === \"string\" && isValidSecretName(name) && deniedNames.includes(name))\n ) {\n return false;\n }\n if (typeof record.owner !== \"string\" || ownerOrUndefined(record.owner) !== record.owner) return false;\n if (\"scope\" in record && (typeof record.scope !== \"string\" || !isValidSecretRequestScope(record.scope))) return false;\n return true;\n}\n\n/** TC-832 scope rule: canonical grammar, at most 64, not reserved. Never canonicalizes. */\nexport function isValidSecretRequestScope(scope: string): boolean {\n return scope.length <= MAX_SCOPE_LENGTH && SCOPE_RE.test(scope) && !RESERVED_SCOPES.includes(scope);\n}\n\n/** TC-832 name rule: `^[A-Z][A-Z0-9_]*$`, at most 64, no trimming. */\nexport function isValidSecretName(name: string): boolean {\n return name.length <= MAX_NAME_LENGTH && NAME_RE.test(name);\n}\n\n/** Validated, de-duplicated (order kept) names, 1-10 of them. */\nexport function validSecretNames(names: readonly string[]): string[] {\n for (const name of names) assertSecretName(name);\n const unique = dedupe(names);\n if (unique.length === 0) {\n throw new SecretRequestInputError(\"INVALID_SECRET_NAME\", \"At least one secret name is required.\");\n }\n if (unique.length > SECRET_REQUEST_MAX_NAMES) {\n throw new SecretRequestInputError(\"TOO_MANY_SECRET_NAMES\", `At most ${SECRET_REQUEST_MAX_NAMES} secret names can be requested at once.`);\n }\n return unique;\n}\n\nfunction assertSecretName(name: string): void {\n if (typeof name !== \"string\" || !isValidSecretName(name)) {\n throw new SecretRequestInputError(\"INVALID_SECRET_NAME\", \"Secret names are 1-64 characters of A-Z, 0-9 and '_', starting with a letter.\");\n }\n}\n\nfunction assertSecretRequestScope(scope: string): void {\n if (typeof scope !== \"string\" || !isValidSecretRequestScope(scope)) {\n throw new SecretRequestInputError(\"INVALID_SECRET_SCOPE\", \"The scope must be a canonical scope of at most 64 characters, not default, global or scoped.\");\n }\n}\n\nfunction isAllowedReturnTo(returnTo: string): boolean {\n if (typeof returnTo !== \"string\" || returnTo.length > MAX_RETURN_TO_LENGTH) return false;\n let url: URL;\n try {\n url = new URL(returnTo);\n } catch {\n return false;\n }\n if (url.username !== \"\" || url.password !== \"\") return false;\n return RETURN_TO_ORIGINS.includes(url.origin) || RETURN_TO_PREVIEW.test(url.origin);\n}\n\nfunction ownerOrUndefined(did: string | undefined): string | undefined {\n if (did === undefined) return undefined;\n try {\n return canonicalSecretRequestOwner(did);\n } catch {\n return undefined;\n }\n}\n\n/** The canonical owner DID of a `tinycloud:pkh:eip155:…` space, or `undefined` for any other space. */\nexport function ownerOfSpace(space: string | undefined): string | undefined {\n const match = space === undefined ? null : PKH_SPACE_RE.exec(space);\n if (match === null) return undefined;\n // Space URIs carry the address in any case; the owner is its EIP-55 form.\n return ownerOrUndefined(`did:pkh:eip155:${match[1]}:${match[2]!.toLowerCase()}`);\n}\n\nfunction putCommands(names: readonly string[], scope: string | undefined): string {\n return dedupe(names)\n .map((name) => `${CLI_NAME} secrets put ${name}${scope === undefined ? \"\" : ` --scope ${scope}`} --stdin`)\n .join(\"; \");\n}\n\nfunction dedupe(names: readonly string[]): string[] {\n return [...new Set(names)];\n}\n\nfunction invalidOwner(): SecretRequestInputError {\n return new SecretRequestInputError(\"INVALID_OWNER\", \"The owner must be a did:pkh:eip155 account with a valid EIP-55 address.\");\n}\n","import { basename } from \"node:path\";\n\n/**\n * The command name hints and messages tell users to run. The CLI installs as\n * both `tc` and `tinycloud` (Ubuntu's iproute2 ships `/usr/sbin/tc`), so the\n * name follows the invoked bin. Any other host process, such as the MCP server,\n * gets `tc`.\n */\nexport const CLI_NAME = basename(process.argv[1] ?? \"\") === \"tinycloud\" ? \"tinycloud\" : \"tc\";\n"],"mappings":"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","names":[]}
|
package/dist/secret-request.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// src/secret-request.ts
|
|
2
2
|
import * as nodeSdk from "@tinycloud/node-sdk";
|
|
3
|
+
|
|
4
|
+
// src/cli-name.ts
|
|
5
|
+
import { basename } from "path";
|
|
6
|
+
var CLI_NAME = basename(process.argv[1] ?? "") === "tinycloud" ? "tinycloud" : "tc";
|
|
7
|
+
|
|
8
|
+
// src/secret-request.ts
|
|
3
9
|
var SECRET_MANAGER_ORIGIN = "https://secrets.tinycloud.xyz";
|
|
4
10
|
var SECRET_REQUEST_MAX_NAMES = 10;
|
|
5
11
|
var SECRET_REQUEST_MAX_URL_LENGTH = 8192;
|
|
@@ -194,7 +200,7 @@ function ownerOfSpace(space) {
|
|
|
194
200
|
return ownerOrUndefined(`did:pkh:eip155:${match[1]}:${match[2].toLowerCase()}`);
|
|
195
201
|
}
|
|
196
202
|
function putCommands(names, scope) {
|
|
197
|
-
return dedupe(names).map((name) =>
|
|
203
|
+
return dedupe(names).map((name) => `${CLI_NAME} secrets put ${name}${scope === void 0 ? "" : ` --scope ${scope}`} --stdin`).join("; ");
|
|
198
204
|
}
|
|
199
205
|
function dedupe(names) {
|
|
200
206
|
return [...new Set(names)];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/secret-request.ts"],"sourcesContent":["// Namespace-shaped so modules that bundle this one stay compatible with\n// lightweight node-sdk test doubles.\nimport * as nodeSdk from \"@tinycloud/node-sdk\";\n\nimport type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * The one builder for Secret Manager links (TC-832 \"Link\"). Every\n * missing-secret path produces its `/request` link here, and `/app` links come\n * only from {@link buildSecretManagerUrl}. No parameter ever carries a value.\n */\nexport const SECRET_MANAGER_ORIGIN = \"https://secrets.tinycloud.xyz\";\nexport const SECRET_REQUEST_MAX_NAMES = 10;\nexport const SECRET_REQUEST_MAX_URL_LENGTH = 8192;\n\nconst OWNER_RE = /^did:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40})$/;\nconst NAME_RE = /^[A-Z][A-Z0-9_]*$/;\nconst MAX_NAME_LENGTH = 64;\nconst SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst MAX_SCOPE_LENGTH = 64;\nconst RESERVED_SCOPES: readonly string[] = [\"default\", \"global\", \"scoped\"];\nconst MAX_REQUESTER_LENGTH = 64;\nconst CONTROL_OR_FORMAT = /[\\p{Cc}\\p{Cf}]/u;\nconst STATE_RE = /^[A-Za-z0-9._~-]{1,128}$/;\nconst MAX_RETURN_TO_LENGTH = 2048;\nconst RETURN_TO_ORIGINS: readonly string[] = [\"https://feed.tinycloud.xyz\"];\nconst RETURN_TO_PREVIEW = /^https:\\/\\/[a-z0-9-]+\\.tinyfeed\\.pages\\.dev$/;\nconst PKH_SPACE_RE = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40}):[^:]+$/;\n\nexport type SecretRequestInputErrorCode =\n | \"INVALID_SECRET_NAME\"\n | \"TOO_MANY_SECRET_NAMES\"\n | \"INVALID_SECRET_SCOPE\"\n | \"INVALID_OWNER\"\n | \"INVALID_REQUESTER\"\n | \"INVALID_RETURN_TO\"\n | \"INVALID_STATE\"\n | \"REQUEST_LINK_TOO_LONG\";\n\nexport class SecretRequestInputError extends Error {\n readonly code: SecretRequestInputErrorCode;\n\n constructor(code: SecretRequestInputErrorCode, message: string) {\n super(message);\n this.name = \"SecretRequestInputError\";\n this.code = code;\n }\n}\n\n/**\n * TC-832 owner rule: `did:pkh:eip155:<chain>:<address>` with a safe-integer\n * chain and a lowercase `0x`. An address with any uppercase letter must\n * already be its EIP-55 form. Returns the DID with the EIP-55 address.\n */\nexport function canonicalSecretRequestOwner(did: string): string {\n const match = typeof did === \"string\" ? OWNER_RE.exec(did) : null;\n if (match === null || !Number.isSafeInteger(Number(match[1]))) throw invalidOwner();\n const address = match[2]!;\n let checksummed: string;\n try {\n checksummed = nodeSdk.canonicalizeAddress(address);\n } catch {\n throw invalidOwner();\n }\n if (/[A-F]/.test(address.slice(2)) && address !== checksummed) throw invalidOwner();\n return `did:pkh:eip155:${match[1]}:${checksummed}`;\n}\n\nexport interface SecretRequestLinkInput {\n readonly owner: string;\n readonly names: readonly string[];\n readonly scope?: string;\n readonly requester?: string;\n readonly returnTo?: string;\n readonly state?: string;\n}\n\nexport interface SecretRequestLink {\n readonly url: string;\n readonly owner: string;\n readonly names: string[];\n readonly scope?: string;\n}\n\n/** Builds the canonical `/request` link, or throws {@link SecretRequestInputError}. */\nexport function buildSecretRequestUrl(input: SecretRequestLinkInput): SecretRequestLink {\n const owner = canonicalSecretRequestOwner(input.owner);\n const names = validSecretNames(input.names);\n if (input.scope !== undefined) assertSecretRequestScope(input.scope);\n const requester = input.requester;\n if (\n requester !== undefined &&\n (typeof requester !== \"string\" || requester.length === 0 || requester.length > MAX_REQUESTER_LENGTH ||\n CONTROL_OR_FORMAT.test(requester))\n ) {\n throw new SecretRequestInputError(\"INVALID_REQUESTER\", \"The requester label must be 1-64 characters with no control or format characters.\");\n }\n if (input.returnTo !== undefined && !isAllowedReturnTo(input.returnTo)) {\n throw new SecretRequestInputError(\"INVALID_RETURN_TO\", \"The return address is not an allowed Secret Manager callback.\");\n }\n if (input.state !== undefined && !STATE_RE.test(input.state)) {\n throw new SecretRequestInputError(\"INVALID_STATE\", \"The state must be 1-128 characters of A-Z, a-z, 0-9, '.', '_', '~' or '-'.\");\n }\n\n const parameters: [string, string][] = [\n [\"owner\", owner],\n ...names.map((name): [string, string] => [\"name\", name]),\n ...(input.scope === undefined ? [] : [[\"scope\", input.scope] as [string, string]]),\n ...(input.requester === undefined ? [] : [[\"requester\", input.requester] as [string, string]]),\n ...(input.returnTo === undefined ? [] : [[\"return_to\", input.returnTo] as [string, string]]),\n ...(input.state === undefined ? [] : [[\"state\", input.state] as [string, string]]),\n [\"v\", \"1\"],\n ];\n const url = `${SECRET_MANAGER_ORIGIN}/request?${parameters\n .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)\n .join(\"&\")}`;\n if (url.length > SECRET_REQUEST_MAX_URL_LENGTH) {\n throw new SecretRequestInputError(\"REQUEST_LINK_TOO_LONG\", `The request link is longer than ${SECRET_REQUEST_MAX_URL_LENGTH} characters.`);\n }\n return { url, owner, names, ...(input.scope === undefined ? {} : { scope: input.scope }) };\n}\n\n/** The value-free setup a missing secret reports: exactly these keys. */\nexport interface SecretSetup {\n readonly url: string;\n readonly names: string[];\n readonly scope?: string;\n readonly owner: string;\n readonly action: \"add_secret\";\n}\n\nexport function secretSetupFor(link: SecretRequestLink): SecretSetup {\n return {\n url: link.url,\n names: [...link.names],\n ...(link.scope === undefined ? {} : { scope: link.scope }),\n owner: link.owner,\n action: \"add_secret\",\n };\n}\n\n/**\n * The full manager (`/app`), the only path that replaces an existing value:\n * bare, `?secret=N`, or `?secrets=A,B`, plus `&scope=`. Names follow the link\n * name rule without the request cap.\n */\nexport function buildSecretManagerUrl(input: { readonly names?: readonly string[]; readonly scope?: string } = {}): string {\n const names = dedupe(input.names ?? []);\n for (const name of names) assertSecretName(name);\n if (input.scope !== undefined) assertSecretRequestScope(input.scope);\n const parameters: [string, string][] = [\n ...(names.length === 1 ? [[\"secret\", names[0]!] as [string, string]] : []),\n ...(names.length > 1 ? [[\"secrets\", names.join(\",\")] as [string, string]] : []),\n ...(input.scope === undefined ? [] : [[\"scope\", input.scope] as [string, string]]),\n ];\n const query = parameters.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(\"&\");\n return `${SECRET_MANAGER_ORIGIN}/app${query === \"\" ? \"\" : `?${query}`}`;\n}\n\nexport type SecretsOwnerResolution =\n | { readonly ok: true; readonly owner: string; readonly local: boolean }\n | { readonly ok: false; readonly code: \"OWNER_DID_UNKNOWN\" | \"OWNER_MISMATCH\"; readonly message: string };\n\n/**\n * The owner whose secrets an identity reads, first match wins: a local key's\n * own account, the recorded owner, a delegate session's space owner, a pkh\n * principal, then the requested owner. A requested owner that disagrees with a\n * resolved one is `OWNER_MISMATCH`.\n */\nexport function resolveSecretsOwner(\n profile: {\n readonly local: boolean;\n readonly posture: TinyCloudPosture;\n readonly ownerDid?: string;\n readonly principalDid?: string;\n readonly space?: string;\n },\n requestedOwner?: string,\n): SecretsOwnerResolution {\n const resolved = profile.local\n ? ownerOrUndefined(profile.principalDid)\n : ownerOrUndefined(profile.ownerDid) ??\n (profile.posture === \"delegate-session\" ? ownerOfSpace(profile.space) : undefined) ??\n ownerOrUndefined(profile.principalDid);\n const requested = requestedOwner === undefined ? undefined : ownerOrUndefined(requestedOwner);\n\n if (resolved !== undefined) {\n if (requestedOwner !== undefined && requested !== resolved) {\n return {\n ok: false,\n code: \"OWNER_MISMATCH\",\n message: \"The requested owner is not the owner of this identity's secrets.\",\n };\n }\n return { ok: true, owner: resolved, local: profile.local };\n }\n if (requested !== undefined && !profile.local) return { ok: true, owner: requested, local: false };\n return {\n ok: false,\n code: \"OWNER_DID_UNKNOWN\",\n message: \"The owner of this identity's secrets is unknown. Pass the owner's did:pkh with --owner.\",\n };\n}\n\nexport type MissingSecretRecovery =\n | { readonly kind: \"add_secret\"; readonly setup: SecretSetup }\n | { readonly kind: \"put_locally\"; readonly hint: string }\n | { readonly kind: \"not_requestable\"; readonly hint: string };\n\n/**\n * How a caller gets missing secrets: a local identity stores them itself; any\n * other identity gets a `/request` link when one can express the request.\n * Never throws.\n */\nexport function missingSecretRecovery(input: {\n readonly owner: string | undefined;\n readonly local: boolean;\n readonly names: readonly string[];\n readonly scope?: string;\n}): MissingSecretRecovery {\n if (input.local) {\n return { kind: \"put_locally\", hint: `Store it with: ${putCommands(input.names, input.scope)}` };\n }\n if (input.owner !== undefined) {\n try {\n return {\n kind: \"add_secret\",\n setup: secretSetupFor(buildSecretRequestUrl({\n owner: input.owner,\n names: input.names,\n ...(input.scope === undefined ? {} : { scope: input.scope }),\n })),\n };\n } catch {\n // A legacy name or scope, or an unusable owner, cannot be requested.\n }\n }\n return {\n kind: \"not_requestable\",\n hint: `No Secret Manager request link can express this secret. The owner can store it with: ${putCommands(input.names, input.scope)}`,\n };\n}\n\n/**\n * The strict policy for a consent denial's `request_access` recovery: exact\n * keys, denied names only, a canonical owner and a valid scope. Any deviation\n * rejects the whole value.\n */\nexport function isSafeRequestAccessRecovery(\n value: unknown,\n deniedNames: readonly string[],\n): value is { action: \"request_access\"; names: string[]; owner: string; scope?: string } {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record);\n if (!keys.every((key) => key === \"action\" || key === \"names\" || key === \"owner\" || key === \"scope\")) return false;\n if (record.action !== \"request_access\") return false;\n const names = record.names;\n if (\n !Array.isArray(names) || names.length === 0 || names.length > SECRET_REQUEST_MAX_NAMES ||\n new Set(names).size !== names.length ||\n !names.every((name) => typeof name === \"string\" && isValidSecretName(name) && deniedNames.includes(name))\n ) {\n return false;\n }\n if (typeof record.owner !== \"string\" || ownerOrUndefined(record.owner) !== record.owner) return false;\n if (\"scope\" in record && (typeof record.scope !== \"string\" || !isValidSecretRequestScope(record.scope))) return false;\n return true;\n}\n\n/** TC-832 scope rule: canonical grammar, at most 64, not reserved. Never canonicalizes. */\nexport function isValidSecretRequestScope(scope: string): boolean {\n return scope.length <= MAX_SCOPE_LENGTH && SCOPE_RE.test(scope) && !RESERVED_SCOPES.includes(scope);\n}\n\n/** TC-832 name rule: `^[A-Z][A-Z0-9_]*$`, at most 64, no trimming. */\nexport function isValidSecretName(name: string): boolean {\n return name.length <= MAX_NAME_LENGTH && NAME_RE.test(name);\n}\n\n/** Validated, de-duplicated (order kept) names, 1-10 of them. */\nexport function validSecretNames(names: readonly string[]): string[] {\n for (const name of names) assertSecretName(name);\n const unique = dedupe(names);\n if (unique.length === 0) {\n throw new SecretRequestInputError(\"INVALID_SECRET_NAME\", \"At least one secret name is required.\");\n }\n if (unique.length > SECRET_REQUEST_MAX_NAMES) {\n throw new SecretRequestInputError(\"TOO_MANY_SECRET_NAMES\", `At most ${SECRET_REQUEST_MAX_NAMES} secret names can be requested at once.`);\n }\n return unique;\n}\n\nfunction assertSecretName(name: string): void {\n if (typeof name !== \"string\" || !isValidSecretName(name)) {\n throw new SecretRequestInputError(\"INVALID_SECRET_NAME\", \"Secret names are 1-64 characters of A-Z, 0-9 and '_', starting with a letter.\");\n }\n}\n\nfunction assertSecretRequestScope(scope: string): void {\n if (typeof scope !== \"string\" || !isValidSecretRequestScope(scope)) {\n throw new SecretRequestInputError(\"INVALID_SECRET_SCOPE\", \"The scope must be a canonical scope of at most 64 characters, not default, global or scoped.\");\n }\n}\n\nfunction isAllowedReturnTo(returnTo: string): boolean {\n if (typeof returnTo !== \"string\" || returnTo.length > MAX_RETURN_TO_LENGTH) return false;\n let url: URL;\n try {\n url = new URL(returnTo);\n } catch {\n return false;\n }\n if (url.username !== \"\" || url.password !== \"\") return false;\n return RETURN_TO_ORIGINS.includes(url.origin) || RETURN_TO_PREVIEW.test(url.origin);\n}\n\nfunction ownerOrUndefined(did: string | undefined): string | undefined {\n if (did === undefined) return undefined;\n try {\n return canonicalSecretRequestOwner(did);\n } catch {\n return undefined;\n }\n}\n\n/** The canonical owner DID of a `tinycloud:pkh:eip155:…` space, or `undefined` for any other space. */\nexport function ownerOfSpace(space: string | undefined): string | undefined {\n const match = space === undefined ? null : PKH_SPACE_RE.exec(space);\n if (match === null) return undefined;\n // Space URIs carry the address in any case; the owner is its EIP-55 form.\n return ownerOrUndefined(`did:pkh:eip155:${match[1]}:${match[2]!.toLowerCase()}`);\n}\n\nfunction putCommands(names: readonly string[], scope: string | undefined): string {\n return dedupe(names)\n .map((name) => `tc secrets put ${name}${scope === undefined ? \"\" : ` --scope ${scope}`} --stdin`)\n .join(\"; \");\n}\n\nfunction dedupe(names: readonly string[]): string[] {\n return [...new Set(names)];\n}\n\nfunction invalidOwner(): SecretRequestInputError {\n return new SecretRequestInputError(\"INVALID_OWNER\", \"The owner must be a did:pkh:eip155 account with a valid EIP-55 address.\");\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/secret-request.ts","../src/cli-name.ts"],"sourcesContent":["// Namespace-shaped so modules that bundle this one stay compatible with\n// lightweight node-sdk test doubles.\nimport * as nodeSdk from \"@tinycloud/node-sdk\";\n\nimport { CLI_NAME } from \"./cli-name.js\";\nimport type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * The one builder for Secret Manager links (TC-832 \"Link\"). Every\n * missing-secret path produces its `/request` link here, and `/app` links come\n * only from {@link buildSecretManagerUrl}. No parameter ever carries a value.\n */\nexport const SECRET_MANAGER_ORIGIN = \"https://secrets.tinycloud.xyz\";\nexport const SECRET_REQUEST_MAX_NAMES = 10;\nexport const SECRET_REQUEST_MAX_URL_LENGTH = 8192;\n\nconst OWNER_RE = /^did:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40})$/;\nconst NAME_RE = /^[A-Z][A-Z0-9_]*$/;\nconst MAX_NAME_LENGTH = 64;\nconst SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst MAX_SCOPE_LENGTH = 64;\nconst RESERVED_SCOPES: readonly string[] = [\"default\", \"global\", \"scoped\"];\nconst MAX_REQUESTER_LENGTH = 64;\nconst CONTROL_OR_FORMAT = /[\\p{Cc}\\p{Cf}]/u;\nconst STATE_RE = /^[A-Za-z0-9._~-]{1,128}$/;\nconst MAX_RETURN_TO_LENGTH = 2048;\nconst RETURN_TO_ORIGINS: readonly string[] = [\"https://feed.tinycloud.xyz\"];\nconst RETURN_TO_PREVIEW = /^https:\\/\\/[a-z0-9-]+\\.tinyfeed\\.pages\\.dev$/;\nconst PKH_SPACE_RE = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40}):[^:]+$/;\n\nexport type SecretRequestInputErrorCode =\n | \"INVALID_SECRET_NAME\"\n | \"TOO_MANY_SECRET_NAMES\"\n | \"INVALID_SECRET_SCOPE\"\n | \"INVALID_OWNER\"\n | \"INVALID_REQUESTER\"\n | \"INVALID_RETURN_TO\"\n | \"INVALID_STATE\"\n | \"REQUEST_LINK_TOO_LONG\";\n\nexport class SecretRequestInputError extends Error {\n readonly code: SecretRequestInputErrorCode;\n\n constructor(code: SecretRequestInputErrorCode, message: string) {\n super(message);\n this.name = \"SecretRequestInputError\";\n this.code = code;\n }\n}\n\n/**\n * TC-832 owner rule: `did:pkh:eip155:<chain>:<address>` with a safe-integer\n * chain and a lowercase `0x`. An address with any uppercase letter must\n * already be its EIP-55 form. Returns the DID with the EIP-55 address.\n */\nexport function canonicalSecretRequestOwner(did: string): string {\n const match = typeof did === \"string\" ? OWNER_RE.exec(did) : null;\n if (match === null || !Number.isSafeInteger(Number(match[1]))) throw invalidOwner();\n const address = match[2]!;\n let checksummed: string;\n try {\n checksummed = nodeSdk.canonicalizeAddress(address);\n } catch {\n throw invalidOwner();\n }\n if (/[A-F]/.test(address.slice(2)) && address !== checksummed) throw invalidOwner();\n return `did:pkh:eip155:${match[1]}:${checksummed}`;\n}\n\nexport interface SecretRequestLinkInput {\n readonly owner: string;\n readonly names: readonly string[];\n readonly scope?: string;\n readonly requester?: string;\n readonly returnTo?: string;\n readonly state?: string;\n}\n\nexport interface SecretRequestLink {\n readonly url: string;\n readonly owner: string;\n readonly names: string[];\n readonly scope?: string;\n}\n\n/** Builds the canonical `/request` link, or throws {@link SecretRequestInputError}. */\nexport function buildSecretRequestUrl(input: SecretRequestLinkInput): SecretRequestLink {\n const owner = canonicalSecretRequestOwner(input.owner);\n const names = validSecretNames(input.names);\n if (input.scope !== undefined) assertSecretRequestScope(input.scope);\n const requester = input.requester;\n if (\n requester !== undefined &&\n (typeof requester !== \"string\" || requester.length === 0 || requester.length > MAX_REQUESTER_LENGTH ||\n CONTROL_OR_FORMAT.test(requester))\n ) {\n throw new SecretRequestInputError(\"INVALID_REQUESTER\", \"The requester label must be 1-64 characters with no control or format characters.\");\n }\n if (input.returnTo !== undefined && !isAllowedReturnTo(input.returnTo)) {\n throw new SecretRequestInputError(\"INVALID_RETURN_TO\", \"The return address is not an allowed Secret Manager callback.\");\n }\n if (input.state !== undefined && !STATE_RE.test(input.state)) {\n throw new SecretRequestInputError(\"INVALID_STATE\", \"The state must be 1-128 characters of A-Z, a-z, 0-9, '.', '_', '~' or '-'.\");\n }\n\n const parameters: [string, string][] = [\n [\"owner\", owner],\n ...names.map((name): [string, string] => [\"name\", name]),\n ...(input.scope === undefined ? [] : [[\"scope\", input.scope] as [string, string]]),\n ...(input.requester === undefined ? [] : [[\"requester\", input.requester] as [string, string]]),\n ...(input.returnTo === undefined ? [] : [[\"return_to\", input.returnTo] as [string, string]]),\n ...(input.state === undefined ? [] : [[\"state\", input.state] as [string, string]]),\n [\"v\", \"1\"],\n ];\n const url = `${SECRET_MANAGER_ORIGIN}/request?${parameters\n .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)\n .join(\"&\")}`;\n if (url.length > SECRET_REQUEST_MAX_URL_LENGTH) {\n throw new SecretRequestInputError(\"REQUEST_LINK_TOO_LONG\", `The request link is longer than ${SECRET_REQUEST_MAX_URL_LENGTH} characters.`);\n }\n return { url, owner, names, ...(input.scope === undefined ? {} : { scope: input.scope }) };\n}\n\n/** The value-free setup a missing secret reports: exactly these keys. */\nexport interface SecretSetup {\n readonly url: string;\n readonly names: string[];\n readonly scope?: string;\n readonly owner: string;\n readonly action: \"add_secret\";\n}\n\nexport function secretSetupFor(link: SecretRequestLink): SecretSetup {\n return {\n url: link.url,\n names: [...link.names],\n ...(link.scope === undefined ? {} : { scope: link.scope }),\n owner: link.owner,\n action: \"add_secret\",\n };\n}\n\n/**\n * The full manager (`/app`), the only path that replaces an existing value:\n * bare, `?secret=N`, or `?secrets=A,B`, plus `&scope=`. Names follow the link\n * name rule without the request cap.\n */\nexport function buildSecretManagerUrl(input: { readonly names?: readonly string[]; readonly scope?: string } = {}): string {\n const names = dedupe(input.names ?? []);\n for (const name of names) assertSecretName(name);\n if (input.scope !== undefined) assertSecretRequestScope(input.scope);\n const parameters: [string, string][] = [\n ...(names.length === 1 ? [[\"secret\", names[0]!] as [string, string]] : []),\n ...(names.length > 1 ? [[\"secrets\", names.join(\",\")] as [string, string]] : []),\n ...(input.scope === undefined ? [] : [[\"scope\", input.scope] as [string, string]]),\n ];\n const query = parameters.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(\"&\");\n return `${SECRET_MANAGER_ORIGIN}/app${query === \"\" ? \"\" : `?${query}`}`;\n}\n\nexport type SecretsOwnerResolution =\n | { readonly ok: true; readonly owner: string; readonly local: boolean }\n | { readonly ok: false; readonly code: \"OWNER_DID_UNKNOWN\" | \"OWNER_MISMATCH\"; readonly message: string };\n\n/**\n * The owner whose secrets an identity reads, first match wins: a local key's\n * own account, the recorded owner, a delegate session's space owner, a pkh\n * principal, then the requested owner. A requested owner that disagrees with a\n * resolved one is `OWNER_MISMATCH`.\n */\nexport function resolveSecretsOwner(\n profile: {\n readonly local: boolean;\n readonly posture: TinyCloudPosture;\n readonly ownerDid?: string;\n readonly principalDid?: string;\n readonly space?: string;\n },\n requestedOwner?: string,\n): SecretsOwnerResolution {\n const resolved = profile.local\n ? ownerOrUndefined(profile.principalDid)\n : ownerOrUndefined(profile.ownerDid) ??\n (profile.posture === \"delegate-session\" ? ownerOfSpace(profile.space) : undefined) ??\n ownerOrUndefined(profile.principalDid);\n const requested = requestedOwner === undefined ? undefined : ownerOrUndefined(requestedOwner);\n\n if (resolved !== undefined) {\n if (requestedOwner !== undefined && requested !== resolved) {\n return {\n ok: false,\n code: \"OWNER_MISMATCH\",\n message: \"The requested owner is not the owner of this identity's secrets.\",\n };\n }\n return { ok: true, owner: resolved, local: profile.local };\n }\n if (requested !== undefined && !profile.local) return { ok: true, owner: requested, local: false };\n return {\n ok: false,\n code: \"OWNER_DID_UNKNOWN\",\n message: \"The owner of this identity's secrets is unknown. Pass the owner's did:pkh with --owner.\",\n };\n}\n\nexport type MissingSecretRecovery =\n | { readonly kind: \"add_secret\"; readonly setup: SecretSetup }\n | { readonly kind: \"put_locally\"; readonly hint: string }\n | { readonly kind: \"not_requestable\"; readonly hint: string };\n\n/**\n * How a caller gets missing secrets: a local identity stores them itself; any\n * other identity gets a `/request` link when one can express the request.\n * Never throws.\n */\nexport function missingSecretRecovery(input: {\n readonly owner: string | undefined;\n readonly local: boolean;\n readonly names: readonly string[];\n readonly scope?: string;\n}): MissingSecretRecovery {\n if (input.local) {\n return { kind: \"put_locally\", hint: `Store it with: ${putCommands(input.names, input.scope)}` };\n }\n if (input.owner !== undefined) {\n try {\n return {\n kind: \"add_secret\",\n setup: secretSetupFor(buildSecretRequestUrl({\n owner: input.owner,\n names: input.names,\n ...(input.scope === undefined ? {} : { scope: input.scope }),\n })),\n };\n } catch {\n // A legacy name or scope, or an unusable owner, cannot be requested.\n }\n }\n return {\n kind: \"not_requestable\",\n hint: `No Secret Manager request link can express this secret. The owner can store it with: ${putCommands(input.names, input.scope)}`,\n };\n}\n\n/**\n * The strict policy for a consent denial's `request_access` recovery: exact\n * keys, denied names only, a canonical owner and a valid scope. Any deviation\n * rejects the whole value.\n */\nexport function isSafeRequestAccessRecovery(\n value: unknown,\n deniedNames: readonly string[],\n): value is { action: \"request_access\"; names: string[]; owner: string; scope?: string } {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record);\n if (!keys.every((key) => key === \"action\" || key === \"names\" || key === \"owner\" || key === \"scope\")) return false;\n if (record.action !== \"request_access\") return false;\n const names = record.names;\n if (\n !Array.isArray(names) || names.length === 0 || names.length > SECRET_REQUEST_MAX_NAMES ||\n new Set(names).size !== names.length ||\n !names.every((name) => typeof name === \"string\" && isValidSecretName(name) && deniedNames.includes(name))\n ) {\n return false;\n }\n if (typeof record.owner !== \"string\" || ownerOrUndefined(record.owner) !== record.owner) return false;\n if (\"scope\" in record && (typeof record.scope !== \"string\" || !isValidSecretRequestScope(record.scope))) return false;\n return true;\n}\n\n/** TC-832 scope rule: canonical grammar, at most 64, not reserved. Never canonicalizes. */\nexport function isValidSecretRequestScope(scope: string): boolean {\n return scope.length <= MAX_SCOPE_LENGTH && SCOPE_RE.test(scope) && !RESERVED_SCOPES.includes(scope);\n}\n\n/** TC-832 name rule: `^[A-Z][A-Z0-9_]*$`, at most 64, no trimming. */\nexport function isValidSecretName(name: string): boolean {\n return name.length <= MAX_NAME_LENGTH && NAME_RE.test(name);\n}\n\n/** Validated, de-duplicated (order kept) names, 1-10 of them. */\nexport function validSecretNames(names: readonly string[]): string[] {\n for (const name of names) assertSecretName(name);\n const unique = dedupe(names);\n if (unique.length === 0) {\n throw new SecretRequestInputError(\"INVALID_SECRET_NAME\", \"At least one secret name is required.\");\n }\n if (unique.length > SECRET_REQUEST_MAX_NAMES) {\n throw new SecretRequestInputError(\"TOO_MANY_SECRET_NAMES\", `At most ${SECRET_REQUEST_MAX_NAMES} secret names can be requested at once.`);\n }\n return unique;\n}\n\nfunction assertSecretName(name: string): void {\n if (typeof name !== \"string\" || !isValidSecretName(name)) {\n throw new SecretRequestInputError(\"INVALID_SECRET_NAME\", \"Secret names are 1-64 characters of A-Z, 0-9 and '_', starting with a letter.\");\n }\n}\n\nfunction assertSecretRequestScope(scope: string): void {\n if (typeof scope !== \"string\" || !isValidSecretRequestScope(scope)) {\n throw new SecretRequestInputError(\"INVALID_SECRET_SCOPE\", \"The scope must be a canonical scope of at most 64 characters, not default, global or scoped.\");\n }\n}\n\nfunction isAllowedReturnTo(returnTo: string): boolean {\n if (typeof returnTo !== \"string\" || returnTo.length > MAX_RETURN_TO_LENGTH) return false;\n let url: URL;\n try {\n url = new URL(returnTo);\n } catch {\n return false;\n }\n if (url.username !== \"\" || url.password !== \"\") return false;\n return RETURN_TO_ORIGINS.includes(url.origin) || RETURN_TO_PREVIEW.test(url.origin);\n}\n\nfunction ownerOrUndefined(did: string | undefined): string | undefined {\n if (did === undefined) return undefined;\n try {\n return canonicalSecretRequestOwner(did);\n } catch {\n return undefined;\n }\n}\n\n/** The canonical owner DID of a `tinycloud:pkh:eip155:…` space, or `undefined` for any other space. */\nexport function ownerOfSpace(space: string | undefined): string | undefined {\n const match = space === undefined ? null : PKH_SPACE_RE.exec(space);\n if (match === null) return undefined;\n // Space URIs carry the address in any case; the owner is its EIP-55 form.\n return ownerOrUndefined(`did:pkh:eip155:${match[1]}:${match[2]!.toLowerCase()}`);\n}\n\nfunction putCommands(names: readonly string[], scope: string | undefined): string {\n return dedupe(names)\n .map((name) => `${CLI_NAME} secrets put ${name}${scope === undefined ? \"\" : ` --scope ${scope}`} --stdin`)\n .join(\"; \");\n}\n\nfunction dedupe(names: readonly string[]): string[] {\n return [...new Set(names)];\n}\n\nfunction invalidOwner(): SecretRequestInputError {\n return new SecretRequestInputError(\"INVALID_OWNER\", \"The owner must be a did:pkh:eip155 account with a valid EIP-55 address.\");\n}\n","import { basename } from \"node:path\";\n\n/**\n * The command name hints and messages tell users to run. The CLI installs as\n * both `tc` and `tinycloud` (Ubuntu's iproute2 ships `/usr/sbin/tc`), so the\n * name follows the invoked bin. Any other host process, such as the MCP server,\n * gets `tc`.\n */\nexport const CLI_NAME = basename(process.argv[1] ?? \"\") === \"tinycloud\" ? \"tinycloud\" : \"tc\";\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinycloud/operations",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3-beta.1",
|
|
4
4
|
"description": "TinyCloud shared operations contracts and runtime",
|
|
5
5
|
"author": "TinyCloud, Inc.",
|
|
6
6
|
"license": "EGPL",
|
|
@@ -54,6 +54,11 @@
|
|
|
54
54
|
"import": "./dist/recovery.js",
|
|
55
55
|
"require": "./dist/recovery.cjs"
|
|
56
56
|
},
|
|
57
|
+
"./cli-name": {
|
|
58
|
+
"types": "./dist/cli-name.d.ts",
|
|
59
|
+
"import": "./dist/cli-name.js",
|
|
60
|
+
"require": "./dist/cli-name.cjs"
|
|
61
|
+
},
|
|
57
62
|
"./operations.json": "./generated/operations.json"
|
|
58
63
|
},
|
|
59
64
|
"engines": {
|
|
@@ -65,7 +70,7 @@
|
|
|
65
70
|
"coverage.json"
|
|
66
71
|
],
|
|
67
72
|
"scripts": {
|
|
68
|
-
"build": "tsup --entry src/index.ts --entry src/state.ts --entry src/artifacts.ts --entry src/cli-runtime.ts --entry src/profile.ts --entry src/secret-capabilities.ts --entry src/delegation-binding.ts --entry src/secret-request.ts --entry src/recovery.ts",
|
|
73
|
+
"build": "tsup --entry src/index.ts --entry src/state.ts --entry src/artifacts.ts --entry src/cli-runtime.ts --entry src/profile.ts --entry src/secret-capabilities.ts --entry src/delegation-binding.ts --entry src/secret-request.ts --entry src/recovery.ts --entry src/cli-name.ts",
|
|
69
74
|
"clean": "rm -rf dist",
|
|
70
75
|
"test": "bun scripts/test.ts",
|
|
71
76
|
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
@@ -74,8 +79,8 @@
|
|
|
74
79
|
"check:generated": "bun scripts/generate.ts --check"
|
|
75
80
|
},
|
|
76
81
|
"dependencies": {
|
|
77
|
-
"@tinycloud/node-sdk": "3.2.
|
|
78
|
-
"@tinycloud/sdk-core": "3.2.
|
|
82
|
+
"@tinycloud/node-sdk": "3.2.3-beta.0",
|
|
83
|
+
"@tinycloud/sdk-core": "3.2.3-beta.0",
|
|
79
84
|
"@tinycloud/sdk-services": "3.2.2",
|
|
80
85
|
"node-sql-parser": "5.4.0",
|
|
81
86
|
"zod": "^3.22.0",
|