@huaweidevtools/aishell-dsh-credentials-store 0.0.14 → 0.0.16
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/lib/accounts.d.ts +31 -20
- package/lib/accounts.d.ts.map +1 -1
- package/lib/accounts.js +57 -24
- package/lib/accounts.js.map +1 -1
- package/lib/{index-JlyJE1E8.d.ts → index-Cdsu1kDC.d.ts} +87 -12
- package/lib/index-Cdsu1kDC.d.ts.map +1 -0
- package/lib/index.d.ts +3 -3
- package/lib/index.js +165 -29
- package/lib/index.js.map +1 -1
- package/lib/{paths-DtFRzop6.js → paths-C0wxA5LO.js} +3 -3
- package/lib/paths-C0wxA5LO.js.map +1 -0
- package/lib/paths.d.ts +10 -9
- package/lib/paths.d.ts.map +1 -1
- package/lib/paths.js +1 -1
- package/lib/{scope-CFetnbG3.d.ts → scope-C7w2eTvE.d.ts} +12 -12
- package/lib/{scope-CFetnbG3.d.ts.map → scope-C7w2eTvE.d.ts.map} +1 -1
- package/lib/{scope-QmrHZaZt.js → scope-DJcK27Uc.js} +12 -12
- package/lib/scope-DJcK27Uc.js.map +1 -0
- package/lib/scope.d.ts +1 -1
- package/lib/scope.js +1 -1
- package/lib/signer-Bx88x7dI.d.ts +72 -0
- package/lib/signer-Bx88x7dI.d.ts.map +1 -0
- package/lib/{signer-BPR0UAF0.js → signer-CVOjpi6y.js} +39 -3
- package/lib/signer-CVOjpi6y.js.map +1 -0
- package/lib/signer.d.ts +2 -2
- package/lib/signer.js +2 -2
- package/lib/types/accounts.d.ts +61 -39
- package/lib/types/accounts.d.ts.map +1 -1
- package/lib/types/accounts.js +108 -52
- package/lib/types/accounts.js.map +1 -1
- package/lib/types/backends/fingerprint.d.ts +78 -0
- package/lib/types/backends/fingerprint.d.ts.map +1 -1
- package/lib/types/backends/fingerprint.js +155 -8
- package/lib/types/backends/fingerprint.js.map +1 -1
- package/lib/types/backends/keyring.d.ts +7 -7
- package/lib/types/backends/keyring.d.ts.map +1 -1
- package/lib/types/backends/keyring.js +15 -9
- package/lib/types/backends/keyring.js.map +1 -1
- package/lib/types/backends/memory.d.ts +4 -17
- package/lib/types/backends/memory.d.ts.map +1 -1
- package/lib/types/backends/memory.js +21 -32
- package/lib/types/backends/memory.js.map +1 -1
- package/lib/types/failover.d.ts +16 -18
- package/lib/types/failover.d.ts.map +1 -1
- package/lib/types/failover.js +19 -21
- package/lib/types/failover.js.map +1 -1
- package/lib/types/index.d.ts +4 -4
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +4 -4
- package/lib/types/index.js.map +1 -1
- package/lib/types/paths.d.ts +10 -9
- package/lib/types/paths.d.ts.map +1 -1
- package/lib/types/paths.js +10 -9
- package/lib/types/paths.js.map +1 -1
- package/lib/types/scope.d.ts +11 -11
- package/lib/types/scope.js +11 -11
- package/lib/types/signer.d.ts +32 -0
- package/lib/types/signer.d.ts.map +1 -1
- package/lib/types/signer.js +40 -5
- package/lib/types/signer.js.map +1 -1
- package/lib/types/verify.d.ts +7 -11
- package/lib/types/verify.d.ts.map +1 -1
- package/lib/types/verify.js +27 -11
- package/lib/types/verify.js.map +1 -1
- package/lib/verify.d.ts +1 -1
- package/lib/verify.d.ts.map +1 -1
- package/lib/verify.js +11 -1
- package/lib/verify.js.map +1 -1
- package/package.json +1 -1
- package/lib/index-JlyJE1E8.d.ts.map +0 -1
- package/lib/paths-DtFRzop6.js.map +0 -1
- package/lib/scope-QmrHZaZt.js.map +0 -1
- package/lib/signer-BPR0UAF0.js.map +0 -1
- package/lib/signer-Et0DIH6o.d.ts +0 -40
- package/lib/signer-Et0DIH6o.d.ts.map +0 -1
|
@@ -18,25 +18,25 @@
|
|
|
18
18
|
* | keyring service name | `aishell-dsh-plugins` |
|
|
19
19
|
* | encrypted file name | `plugins.credentials.enc` |
|
|
20
20
|
*
|
|
21
|
-
* ## Why it is
|
|
21
|
+
* ## Why it is a build-time constant
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* The value has to be the same on both sides of a credential: the writer that
|
|
24
|
+
* stores it and the reader that looks it up. A runtime setting can be applied
|
|
25
|
+
* to one plugin and not another, and the result is a form that says "saved"
|
|
26
|
+
* beside tools that say "not authenticated" — a failure whose only symptom is
|
|
27
|
+
* the one thing the setting exists to control.
|
|
28
28
|
*
|
|
29
29
|
* A constant cannot be set on one side only. Two deployments that must not see
|
|
30
30
|
* each other's credentials (a staging profile beside a production one) differ
|
|
31
31
|
* by *building* differently, which is the honest place for a persistence
|
|
32
32
|
* boundary.
|
|
33
33
|
*
|
|
34
|
-
* ## Why
|
|
34
|
+
* ## Why the default namespace carries no migration path
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* a
|
|
36
|
+
* `plugins` is the first and only scope, so a build that sets nothing lands on
|
|
37
|
+
* it and a build that sets something has deliberately moved to a namespace of
|
|
38
|
+
* its own. There are no two names to reconcile, so there is no migration path
|
|
39
|
+
* to write — and none is needed until a release renames this default.
|
|
40
40
|
*
|
|
41
41
|
* @module @huaweidevtools/aishell-dsh-credentials-store/scope
|
|
42
42
|
*/
|
|
@@ -93,4 +93,4 @@ const CREDENTIALS_FILE_NAME = `${CREDENTIAL_SCOPE}.credentials.enc`;
|
|
|
93
93
|
|
|
94
94
|
//#endregion
|
|
95
95
|
export { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE };
|
|
96
|
-
//# sourceMappingURL=scope-
|
|
96
|
+
//# sourceMappingURL=scope-DJcK27Uc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-DJcK27Uc.js","names":[],"sources":["../src/scope.ts"],"sourcesContent":["/**\n * Which credential namespace this build uses.\n *\n * ## The knob\n *\n * A build-time constant, not a runtime setting:\n *\n * ```sh\n * AISHELL_CREDENTIAL_SCOPE=staging pnpm run build\n * ```\n *\n * Default `plugins`. It decides two things, and it decides both at once —\n * which is the point:\n *\n * | Used as | Default |\n * |---|---|\n * | keyring service name | `aishell-dsh-plugins` |\n * | encrypted file name | `plugins.credentials.enc` |\n *\n * ## Why it is a build-time constant\n *\n * The value has to be the same on both sides of a credential: the writer that\n * stores it and the reader that looks it up. A runtime setting can be applied\n * to one plugin and not another, and the result is a form that says \"saved\"\n * beside tools that say \"not authenticated\" — a failure whose only symptom is\n * the one thing the setting exists to control.\n *\n * A constant cannot be set on one side only. Two deployments that must not see\n * each other's credentials (a staging profile beside a production one) differ\n * by *building* differently, which is the honest place for a persistence\n * boundary.\n *\n * ## Why the default namespace carries no migration path\n *\n * `plugins` is the first and only scope, so a build that sets nothing lands on\n * it and a build that sets something has deliberately moved to a namespace of\n * its own. There are no two names to reconcile, so there is no migration path\n * to write — and none is needed until a release renames this default.\n *\n * @module @huaweidevtools/aishell-dsh-credentials-store/scope\n */\n\n/**\n * The default scope.\n *\n * Named for the repository, not for a feature: the credential store is shared\n * by every plugin in this set, so it is not `llm`'s or `e2b`'s.\n */\nexport const DEFAULT_SCOPE = 'plugins'\n\n/**\n * Injected at build time; see `tsdown.config.ts`.\n *\n * Declared as possibly-absent because a source run (the test suite, which\n * resolves `src/` directly) never passes through the bundler. The `typeof`\n * guard below handles that without a `ReferenceError`.\n */\ndeclare const __AISHELL_CREDENTIAL_SCOPE__: string | undefined\n\n/**\n * Accepted scope shape: a lowercase identifier.\n *\n * The value reaches a keyring service name and a file name, so it is validated\n * as a strict identifier rather than merely \"not obviously a path\": no\n * separators, no dots, no leading dash. Assuming a value is benign because the\n * caller is trusted is how path traversal gets in.\n */\nconst SCOPE_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/\n\n/**\n * Resolve the scope for this build.\n *\n * Fails loudly on a malformed value rather than falling back to the default:\n * a typo would otherwise silently share the namespace with every unset build,\n * which is exactly the confusion scoping exists to avoid.\n *\n * @returns the validated scope.\n * @throws when the injected value is not a valid scope.\n */\nfunction resolveScope(): string {\n // `typeof` rather than a direct read: an undeclared identifier throws, and a\n // source run legitimately has no injected value.\n const injected = typeof __AISHELL_CREDENTIAL_SCOPE__ === 'string'\n ? __AISHELL_CREDENTIAL_SCOPE__\n : undefined\n if (injected === undefined || injected === '') return DEFAULT_SCOPE\n const normalized = injected.trim().toLowerCase()\n if (normalized.length === 0) return DEFAULT_SCOPE\n if (!SCOPE_PATTERN.test(normalized)) {\n throw new Error(\n `AISHELL_CREDENTIAL_SCOPE=${JSON.stringify(injected)} is not a valid credential scope: `\n + `expected 1-64 characters of [a-z0-9_-], starting with a letter or digit `\n + '(for example \"plugins\" or \"staging\").',\n )\n }\n return normalized\n}\n\n/** The scope this build uses. */\nexport const CREDENTIAL_SCOPE = resolveScope()\n\n/**\n * The keyring service every entry of this build lives under.\n *\n * Part of the persistence contract: renaming it orphans every stored entry\n * while looking like a harmless constant change.\n */\nexport const KEYRING_SERVICE = `aishell-dsh-${CREDENTIAL_SCOPE}`\n\n/**\n * The encrypted file name for this build.\n *\n * Scoped the same way as the keyring service, so two builds on one machine\n * cannot collide in either backend.\n */\nexport const CREDENTIALS_FILE_NAME = `${CREDENTIAL_SCOPE}.credentials.enc`"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,MAAa,gBAAgB;;;;;;;;;AAmB7B,MAAM,gBAAgB;;;;;;;;;;;AAYtB,SAAS,eAAuB;CAG9B,MAAM;AAGN,KAAI,uBAA0B,aAAa,GAAI,QAAO;CACtD,MAAM,aAAa,SAAS,MAAM,CAAC,aAAa;AAChD,KAAI,WAAW,WAAW,EAAG,QAAO;AACpC,MAAK,cAAc,KAAK,WAAW,CACjC,OAAM,IAAI,OACP,2BAA2B,KAAK,UAAU,SAAS,CAAC;AAKzD,QAAO;AACR;;AAGD,MAAa,mBAAmB,cAAc;;;;;;;AAQ9C,MAAa,mBAAmB,cAAc,iBAAiB;;;;;;;AAQ/D,MAAa,yBAAyB,EAAE,iBAAiB"}
|
package/lib/scope.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { CREDENTIALS_FILE_NAME$1 as CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE$1 as CREDENTIAL_SCOPE, DEFAULT_SCOPE$1 as DEFAULT_SCOPE, KEYRING_SERVICE$1 as KEYRING_SERVICE } from "./scope-
|
|
1
|
+
import { CREDENTIALS_FILE_NAME$1 as CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE$1 as CREDENTIAL_SCOPE, DEFAULT_SCOPE$1 as DEFAULT_SCOPE, KEYRING_SERVICE$1 as KEYRING_SERVICE } from "./scope-C7w2eTvE.js";
|
|
2
2
|
export { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE };
|
package/lib/scope.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE } from "./scope-
|
|
1
|
+
import { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE } from "./scope-DJcK27Uc.js";
|
|
2
2
|
|
|
3
3
|
export { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//#region src/signer.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Huawei Cloud SDK-HMAC-SHA256 request signing.
|
|
4
|
+
*
|
|
5
|
+
* Signs an HTTP request with AK/SK (and an optional temporary SecurityToken)
|
|
6
|
+
* using the same algorithm as the official Huawei Cloud SDK. Produces the
|
|
7
|
+
* `Authorization`, `x-sdk-date`, and `x-security-token` headers to attach
|
|
8
|
+
* to a `fetch` call.
|
|
9
|
+
*
|
|
10
|
+
* @module @huaweidevtools/aishell-dsh-credentials-store/signer
|
|
11
|
+
*/
|
|
12
|
+
/** Credentials for signing. */
|
|
13
|
+
interface SignCredentials {
|
|
14
|
+
readonly ak: string;
|
|
15
|
+
readonly sk: string;
|
|
16
|
+
readonly securityToken?: string;
|
|
17
|
+
}
|
|
18
|
+
/** The headers produced by {@link sign}. */
|
|
19
|
+
interface SignedHeaders {
|
|
20
|
+
readonly host: string;
|
|
21
|
+
readonly 'x-sdk-date': string;
|
|
22
|
+
readonly Authorization: string;
|
|
23
|
+
readonly 'x-security-token'?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Code-unit comparison — the order the signing specification requires.
|
|
27
|
+
*
|
|
28
|
+
* Exported because it is the part of signing that is checkable without a
|
|
29
|
+
* signature: everything downstream of it is a hash, so a test that compares two
|
|
30
|
+
* signatures cannot tell a correct ordering from a consistently wrong one. See
|
|
31
|
+
* `tests/format.spec.ts`.
|
|
32
|
+
*
|
|
33
|
+
* @param a - one string.
|
|
34
|
+
* @param b - the other.
|
|
35
|
+
* @returns negative, zero or positive in code-unit order.
|
|
36
|
+
*/
|
|
37
|
+
declare function compareCodeUnits(a: string, b: string): number;
|
|
38
|
+
/**
|
|
39
|
+
* Build the canonical query string, sorted by parameter name.
|
|
40
|
+
*
|
|
41
|
+
* ## Why byte order and not `localeCompare`
|
|
42
|
+
*
|
|
43
|
+
* The specification sorts by code point. Locale-aware collation disagrees with
|
|
44
|
+
* it on mixed case: `Zebra` sorts *before* `apple` by code unit and *after* it
|
|
45
|
+
* under the default locale. The signed string would then differ from the one
|
|
46
|
+
* the gateway rebuilds from the same URL, and the request fails to authenticate
|
|
47
|
+
* with nothing in the error naming the sort.
|
|
48
|
+
*
|
|
49
|
+
* Every caller in this repo passes lowercase ASCII parameter names today, which
|
|
50
|
+
* is why this survived; `openapi_request` takes a URL from the model, so mixed
|
|
51
|
+
* case reaches this path from outside.
|
|
52
|
+
*
|
|
53
|
+
* @param query - the query parameters.
|
|
54
|
+
* @returns the canonical string, `k=v&k=v`, with both sides URL-encoded.
|
|
55
|
+
*/
|
|
56
|
+
declare function canonicalQuery(query: Record<string, string>): string;
|
|
57
|
+
/**
|
|
58
|
+
* Sign an HTTP request with SDK-HMAC-SHA256.
|
|
59
|
+
*
|
|
60
|
+
* @param method - HTTP method (GET, POST, ...).
|
|
61
|
+
* @param host - Host component of the request URL (e.g. `openai.aishell.openbuildlab.com`).
|
|
62
|
+
* @param path - Path component of the request URL (e.g. `/chat/completions`).
|
|
63
|
+
* @param query - Query parameters as a key-value map.
|
|
64
|
+
* @param body - Request body (stringified for hashing; empty string for GET).
|
|
65
|
+
* @param credentials - AK/SK and optional SecurityToken.
|
|
66
|
+
* @returns The headers to attach to the fetch call.
|
|
67
|
+
*/
|
|
68
|
+
declare function sign(method: string, host: string, path: string, query: Record<string, string>, body: string, credentials: SignCredentials): SignedHeaders; //#endregion
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=signer.d.ts.map
|
|
71
|
+
export { SignCredentials, SignedHeaders, canonicalQuery as canonicalQuery$1, compareCodeUnits as compareCodeUnits$1, sign as sign$1 };
|
|
72
|
+
//# sourceMappingURL=signer-Bx88x7dI.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer-Bx88x7dI.d.ts","names":[],"sources":["../../src/signer.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;UAciB,eAAA;EAAA,SAAA,EAAA,EAAA,MAAe;;EAOf,SAAA,aAAa,CAAA,EAAA,MAAA;;;UAAb,aAAA;;;;;;;;;;AAkDjB;;;;;;;;iBAAgB,gBAAA;;;;;;;;;;;AAsBhB;;;;;;;;iBAAgB,cAAA,QAAsB;;;;AAkBtC;;;;;AAOgB;;;iBAPA,IAAA,oDAIP,mDAEM,kBACZ"}
|
|
@@ -24,6 +24,42 @@ function sdkTimestamp() {
|
|
|
24
24
|
return new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d+/, "");
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
+
* Code-unit comparison — the order the signing specification requires.
|
|
28
|
+
*
|
|
29
|
+
* Exported because it is the part of signing that is checkable without a
|
|
30
|
+
* signature: everything downstream of it is a hash, so a test that compares two
|
|
31
|
+
* signatures cannot tell a correct ordering from a consistently wrong one. See
|
|
32
|
+
* `tests/format.spec.ts`.
|
|
33
|
+
*
|
|
34
|
+
* @param a - one string.
|
|
35
|
+
* @param b - the other.
|
|
36
|
+
* @returns negative, zero or positive in code-unit order.
|
|
37
|
+
*/
|
|
38
|
+
function compareCodeUnits(a, b) {
|
|
39
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build the canonical query string, sorted by parameter name.
|
|
43
|
+
*
|
|
44
|
+
* ## Why byte order and not `localeCompare`
|
|
45
|
+
*
|
|
46
|
+
* The specification sorts by code point. Locale-aware collation disagrees with
|
|
47
|
+
* it on mixed case: `Zebra` sorts *before* `apple` by code unit and *after* it
|
|
48
|
+
* under the default locale. The signed string would then differ from the one
|
|
49
|
+
* the gateway rebuilds from the same URL, and the request fails to authenticate
|
|
50
|
+
* with nothing in the error naming the sort.
|
|
51
|
+
*
|
|
52
|
+
* Every caller in this repo passes lowercase ASCII parameter names today, which
|
|
53
|
+
* is why this survived; `openapi_request` takes a URL from the model, so mixed
|
|
54
|
+
* case reaches this path from outside.
|
|
55
|
+
*
|
|
56
|
+
* @param query - the query parameters.
|
|
57
|
+
* @returns the canonical string, `k=v&k=v`, with both sides URL-encoded.
|
|
58
|
+
*/
|
|
59
|
+
function canonicalQuery(query) {
|
|
60
|
+
return Object.entries(query).sort(([a], [b]) => compareCodeUnits(a, b)).map(([k, v]) => `${urlEncode(k)}=${urlEncode(v)}`).join("&");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
27
63
|
* Sign an HTTP request with SDK-HMAC-SHA256.
|
|
28
64
|
*
|
|
29
65
|
* @param method - HTTP method (GET, POST, ...).
|
|
@@ -36,7 +72,7 @@ function sdkTimestamp() {
|
|
|
36
72
|
*/
|
|
37
73
|
function sign(method, host, path, query, body, credentials) {
|
|
38
74
|
const ts = sdkTimestamp();
|
|
39
|
-
const cqs =
|
|
75
|
+
const cqs = canonicalQuery(query);
|
|
40
76
|
const curi = "/" + path.split("/").filter(Boolean).map(urlEncode).join("/") + "/";
|
|
41
77
|
const hasToken = credentials.securityToken !== void 0;
|
|
42
78
|
const signedHeaders = hasToken ? "host;x-sdk-date;x-security-token" : "host;x-sdk-date";
|
|
@@ -62,5 +98,5 @@ function sign(method, host, path, query, body, credentials) {
|
|
|
62
98
|
}
|
|
63
99
|
|
|
64
100
|
//#endregion
|
|
65
|
-
export { sign };
|
|
66
|
-
//# sourceMappingURL=signer-
|
|
101
|
+
export { canonicalQuery, compareCodeUnits, sign };
|
|
102
|
+
//# sourceMappingURL=signer-CVOjpi6y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer-CVOjpi6y.js","names":["data: string","key: string","str: string","c: number","a: string","b: string","query: Record<string, string>","method: string","host: string","path: string","body: string","credentials: SignCredentials","headers: SignedHeaders"],"sources":["../src/signer.ts"],"sourcesContent":["/**\n * Huawei Cloud SDK-HMAC-SHA256 request signing.\n *\n * Signs an HTTP request with AK/SK (and an optional temporary SecurityToken)\n * using the same algorithm as the official Huawei Cloud SDK. Produces the\n * `Authorization`, `x-sdk-date`, and `x-security-token` headers to attach\n * to a `fetch` call.\n *\n * @module @huaweidevtools/aishell-dsh-credentials-store/signer\n */\n\nimport crypto from 'node:crypto'\n\n/** Credentials for signing. */\nexport interface SignCredentials {\n readonly ak: string\n readonly sk: string\n readonly securityToken?: string\n}\n\n/** The headers produced by {@link sign}. */\nexport interface SignedHeaders {\n readonly host: string\n readonly 'x-sdk-date': string\n readonly Authorization: string\n readonly 'x-security-token'?: string\n}\n\nfunction sha256Hex(data: string): string {\n return crypto.createHash('sha256').update(data).digest('hex')\n}\n\nfunction hmacSha256(key: string, data: string): string {\n return crypto.createHmac('sha256', key).update(data).digest('hex')\n}\n\n/** RFC 3986 URL-encode a string (unreserved chars stay literal). */\nfunction urlEncode(str: string): string {\n const hex = (c: number): string => '%' + (c < 16 ? '0' : '') + c.toString(16).toUpperCase()\n const noEscape = new Set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split(''))\n let out = ''\n for (const ch of str) {\n const c = ch.codePointAt(0)!\n if (c < 0x80) {\n out += noEscape.has(ch) ? ch : hex(c)\n } else {\n out += encodeURIComponent(ch)\n }\n }\n return out\n}\n\n/** ISO 8601 timestamp in Huawei Cloud format: `20260912T064552Z`. */\nfunction sdkTimestamp(): string {\n // toISOString() returns \"2026-09-12T06:45:52.123Z\"; strip separators and\n // milliseconds but keep the trailing Z — do NOT append another one.\n return new Date().toISOString().replace(/[-:]/g, '').replace(/\\.\\d+/, '')\n}\n\n/**\n * Code-unit comparison — the order the signing specification requires.\n *\n * Exported because it is the part of signing that is checkable without a\n * signature: everything downstream of it is a hash, so a test that compares two\n * signatures cannot tell a correct ordering from a consistently wrong one. See\n * `tests/format.spec.ts`.\n *\n * @param a - one string.\n * @param b - the other.\n * @returns negative, zero or positive in code-unit order.\n */\nexport function compareCodeUnits(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0\n}\n\n/**\n * Build the canonical query string, sorted by parameter name.\n *\n * ## Why byte order and not `localeCompare`\n *\n * The specification sorts by code point. Locale-aware collation disagrees with\n * it on mixed case: `Zebra` sorts *before* `apple` by code unit and *after* it\n * under the default locale. The signed string would then differ from the one\n * the gateway rebuilds from the same URL, and the request fails to authenticate\n * with nothing in the error naming the sort.\n *\n * Every caller in this repo passes lowercase ASCII parameter names today, which\n * is why this survived; `openapi_request` takes a URL from the model, so mixed\n * case reaches this path from outside.\n *\n * @param query - the query parameters.\n * @returns the canonical string, `k=v&k=v`, with both sides URL-encoded.\n */\nexport function canonicalQuery(query: Record<string, string>): string {\n return Object.entries(query)\n .sort(([a], [b]) => compareCodeUnits(a, b))\n .map(([k, v]) => `${urlEncode(k)}=${urlEncode(v)}`)\n .join('&')\n}\n\n/**\n * Sign an HTTP request with SDK-HMAC-SHA256.\n *\n * @param method - HTTP method (GET, POST, ...).\n * @param host - Host component of the request URL (e.g. `openai.aishell.openbuildlab.com`).\n * @param path - Path component of the request URL (e.g. `/chat/completions`).\n * @param query - Query parameters as a key-value map.\n * @param body - Request body (stringified for hashing; empty string for GET).\n * @param credentials - AK/SK and optional SecurityToken.\n * @returns The headers to attach to the fetch call.\n */\nexport function sign(\n method: string,\n host: string,\n path: string,\n query: Record<string, string>,\n body: string,\n credentials: SignCredentials,\n): SignedHeaders {\n const ts = sdkTimestamp()\n const cqs = canonicalQuery(query)\n\n // Canonical URI: path segments URL-encoded, joined with /, trailing /\n const curi = '/' + path.split('/').filter(Boolean).map(urlEncode).join('/') + '/'\n\n // Signed headers\n const hasToken = credentials.securityToken !== undefined\n const signedHeaders = hasToken ? 'host;x-sdk-date;x-security-token' : 'host;x-sdk-date'\n const canonicalHeaders = hasToken\n ? `host:${host}\\nx-sdk-date:${ts}\\nx-security-token:${credentials.securityToken}\\n`\n : `host:${host}\\nx-sdk-date:${ts}\\n`\n\n // Payload hash\n const payloadHash = sha256Hex(body)\n\n // Canonical request\n const canonicalRequest = [method, curi, cqs, canonicalHeaders, signedHeaders, payloadHash].join('\\n')\n\n // String to sign\n const stringToSign = `SDK-HMAC-SHA256\\n${ts}\\n${sha256Hex(canonicalRequest)}`\n const signature = hmacSha256(credentials.sk, stringToSign)\n\n const headers: SignedHeaders = {\n host,\n 'x-sdk-date': ts,\n Authorization: `SDK-HMAC-SHA256 Access=${credentials.ak}, SignedHeaders=${signedHeaders}, Signature=${signature}`,\n ...(hasToken ? { 'x-security-token': credentials.securityToken } : {}),\n }\n return headers\n}\n"],"mappings":";;;AA4BA,SAAS,UAAUA,MAAsB;AACvC,QAAO,OAAO,WAAW,SAAS,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM;AAC9D;AAED,SAAS,WAAWC,KAAaD,MAAsB;AACrD,QAAO,OAAO,WAAW,UAAU,IAAI,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM;AACnE;;AAGD,SAAS,UAAUE,KAAqB;CACtC,MAAM,MAAM,CAACC,MAAsB,OAAO,IAAI,KAAK,MAAM,MAAM,EAAE,SAAS,GAAG,CAAC,aAAa;CAC3F,MAAM,WAAW,IAAI,IAAI,qEAAqE,MAAM,GAAG;CACvG,IAAI,MAAM;AACV,MAAK,MAAM,MAAM,KAAK;EACpB,MAAM,IAAI,GAAG,YAAY,EAAE;AAC3B,MAAI,IAAI,IACN,QAAO,SAAS,IAAI,GAAG,GAAG,KAAK,IAAI,EAAE;MAErC,QAAO,mBAAmB,GAAG;CAEhC;AACD,QAAO;AACR;;AAGD,SAAS,eAAuB;AAG9B,QAAO,IAAI,OAAO,aAAa,CAAC,QAAQ,SAAS,GAAG,CAAC,QAAQ,SAAS,GAAG;AAC1E;;;;;;;;;;;;;AAcD,SAAgB,iBAAiBC,GAAWC,GAAmB;AAC7D,QAAO,IAAI,IAAA,KAAS,IAAI,IAAI,IAAI;AACjC;;;;;;;;;;;;;;;;;;;AAoBD,SAAgB,eAAeC,OAAuC;AACpE,QAAO,OAAO,QAAQ,MAAM,CACzB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,iBAAiB,GAAG,EAAE,CAAC,CAC1C,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,CAClD,KAAK,IAAI;AACb;;;;;;;;;;;;AAaD,SAAgB,KACdC,QACAC,MACAC,MACAH,OACAI,MACAC,aACe;CACf,MAAM,KAAK,cAAc;CACzB,MAAM,MAAM,eAAe,MAAM;CAGjC,MAAM,OAAO,MAAM,KAAK,MAAM,IAAI,CAAC,OAAO,QAAQ,CAAC,IAAI,UAAU,CAAC,KAAK,IAAI,GAAG;CAG9E,MAAM,WAAW,YAAY;CAC7B,MAAM,gBAAgB,WAAW,qCAAqC;CACtE,MAAM,mBAAmB,YACpB,OAAO,KAAK,eAAe,GAAG,qBAAqB,YAAY,cAAc,OAC7E,OAAO,KAAK,eAAe,GAAG;CAGnC,MAAM,cAAc,UAAU,KAAK;CAGnC,MAAM,mBAAmB;EAAC;EAAQ;EAAM;EAAK;EAAkB;EAAe;CAAY,EAAC,KAAK,KAAK;CAGrG,MAAM,gBAAgB,mBAAmB,GAAG,IAAI,UAAU,iBAAiB,CAAC;CAC5E,MAAM,YAAY,WAAW,YAAY,IAAI,aAAa;CAE1D,MAAMC,UAAyB;EAC7B;EACA,cAAc;EACd,gBAAgB,yBAAyB,YAAY,GAAG,kBAAkB,cAAc,cAAc,UAAU;EAChH,GAAI,WAAW,EAAE,oBAAoB,YAAY,cAAe,IAAG,CAAE;CACtE;AACD,QAAO;AACR"}
|
package/lib/signer.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { SignCredentials, SignedHeaders, sign$1 as sign } from "./signer-
|
|
2
|
-
export { SignCredentials, SignedHeaders, sign };
|
|
1
|
+
import { SignCredentials, SignedHeaders, canonicalQuery$1 as canonicalQuery, compareCodeUnits$1 as compareCodeUnits, sign$1 as sign } from "./signer-Bx88x7dI.js";
|
|
2
|
+
export { SignCredentials, SignedHeaders, canonicalQuery, compareCodeUnits, sign };
|
package/lib/signer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { sign } from "./signer-
|
|
1
|
+
import { canonicalQuery, compareCodeUnits, sign } from "./signer-CVOjpi6y.js";
|
|
2
2
|
|
|
3
|
-
export { sign };
|
|
3
|
+
export { canonicalQuery, compareCodeUnits, sign };
|
package/lib/types/accounts.d.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Huawei Cloud credential vocabulary every plugin in this set shares.
|
|
3
3
|
*
|
|
4
|
-
* ## Why
|
|
4
|
+
* ## Why one module owns the account names
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the cause would be invisible in the diff that introduced it.
|
|
6
|
+
* The account names and the "AK and SK are one unit" rule are a *persistence*
|
|
7
|
+
* contract. A second declaration of either does not fail a build: it surfaces
|
|
8
|
+
* as one plugin working and another reporting "not authenticated" on a machine
|
|
9
|
+
* where exactly one pair is stored, and the diff that introduced it looks
|
|
10
|
+
* harmless. Keeping them in one file makes the contract have one owner.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* ## One entry, not three
|
|
12
|
+
* ## One record, holding the whole bundle
|
|
16
13
|
*
|
|
17
14
|
* The store holds **one** record, `aishell-credential`, whose value is a single
|
|
18
15
|
* JSON bundle encoded as base64:
|
|
@@ -24,18 +21,16 @@
|
|
|
24
21
|
* }
|
|
25
22
|
* ```
|
|
26
23
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* - **A save was four operations.** Clear, then write AK, then SK, then
|
|
31
|
-
* token. Every backend is per-entry, so a failure between them left a
|
|
32
|
-
* half-set behind, and nothing rolled it back.
|
|
33
|
-
* - **A read was three reads.** A concurrent write landing between them could
|
|
34
|
-
* hand back a new AK with an old SK — a pair that passes "both present" and
|
|
35
|
-
* then fails at the gateway with 401, pointing nowhere near the cause.
|
|
24
|
+
* One record is what makes a save one operation and a read one consistent
|
|
25
|
+
* snapshot:
|
|
36
26
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
27
|
+
* - **A save is one write.** Splitting the pair across entries makes every
|
|
28
|
+
* save a sequence, and every backend is per-entry, so a failure between two
|
|
29
|
+
* of them leaves a half-set behind that nothing rolls back.
|
|
30
|
+
* - **A read is one read.** Reading the AK and the SK separately lets a
|
|
31
|
+
* concurrent write land between them: the caller gets a new AK with an old
|
|
32
|
+
* SK, which passes "both present" and then fails at the gateway with 401,
|
|
33
|
+
* pointing nowhere near the cause.
|
|
39
34
|
*
|
|
40
35
|
* ## base64 is an encoding, not encryption
|
|
41
36
|
*
|
|
@@ -50,9 +45,25 @@
|
|
|
50
45
|
* back. Two processes doing that concurrently can lose one of the changes. That
|
|
51
46
|
* is accepted rather than solved: the only writer is a person via the
|
|
52
47
|
* credential form, and both the e2b key and the AK/SK are refreshed by that
|
|
53
|
-
* same person
|
|
48
|
+
* same person, so a lost update is corrected by the next save. Solving it
|
|
54
49
|
* properly needs compare-and-swap, which the keyring API does not offer.
|
|
55
50
|
*
|
|
51
|
+
* ## One accepted consequence: a partial bundle can overwrite an unreadable one
|
|
52
|
+
*
|
|
53
|
+
* A read that cannot decrypt the file reports "nothing stored", so an
|
|
54
|
+
* {@link updateCredentials} built on it computes its next bundle from an empty
|
|
55
|
+
* one and writes that. For `clearE2BKey` that means the written record carries
|
|
56
|
+
* only whatever the updater added, and the section it could not read is no
|
|
57
|
+
* longer in the live file.
|
|
58
|
+
*
|
|
59
|
+
* That is not repaired here, because there is nothing to repair it *from* — the
|
|
60
|
+
* bytes are unreadable. What limits the damage is the file backend's write path:
|
|
61
|
+
* an unreadable file is moved to `<name>.unreadable-<timestamp>` rather than
|
|
62
|
+
* overwritten, so the old record survives on disk and the next successful save
|
|
63
|
+
* starts from an empty map. A reader who cannot decrypt the file has already
|
|
64
|
+
* lost those credentials; the guarantee is only that the file they were in is
|
|
65
|
+
* not destroyed.
|
|
66
|
+
*
|
|
56
67
|
* @module @huaweidevtools/aishell-dsh-credentials-store/accounts
|
|
57
68
|
*/
|
|
58
69
|
import type { CredentialsStore } from './index.ts';
|
|
@@ -65,18 +76,22 @@ import type { SignCredentials } from './signer.ts';
|
|
|
65
76
|
*/
|
|
66
77
|
export declare const CREDENTIAL_ACCOUNT = "aishell-credential";
|
|
67
78
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
79
|
+
* The per-field account names a store may still hold beside the bundle.
|
|
80
|
+
*
|
|
81
|
+
* These are the read-side names of a store written field-by-field instead of
|
|
82
|
+
* as one record. {@link readCredentials} folds them into
|
|
83
|
+
* {@link CREDENTIAL_ACCOUNT} on first read, so a machine that holds them keeps
|
|
84
|
+
* working; nothing writes them.
|
|
70
85
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
86
|
+
* The order is the order the migration reads them: access key, secret key,
|
|
87
|
+
* token.
|
|
73
88
|
*/
|
|
74
89
|
export declare const LEGACY_ACCOUNTS: readonly ["HW_ACCESS_KEY", "HW_SECRET_KEY", "HW_SECURITY_TOKEN"];
|
|
75
90
|
/**
|
|
76
|
-
* The
|
|
91
|
+
* The standalone e2b key entry, beside the per-field names above.
|
|
77
92
|
*
|
|
78
|
-
* Folded into the bundle by {@link readCredentials}
|
|
79
|
-
* the
|
|
93
|
+
* Folded into the bundle by {@link readCredentials}, for the same reason: a
|
|
94
|
+
* store written before the bundle existed keeps working. Nothing writes it.
|
|
80
95
|
*/
|
|
81
96
|
export declare const LEGACY_E2B_ACCOUNT = "AISHELL_E2B_API_KEY";
|
|
82
97
|
/** The Huawei Cloud pair, as the bundle stores it. Snake-case on disk. */
|
|
@@ -128,12 +143,12 @@ export declare function encodeBundle(bundle: CredentialBundle): string;
|
|
|
128
143
|
*/
|
|
129
144
|
export declare function decodeBundle(raw: string | undefined): CredentialBundle;
|
|
130
145
|
/**
|
|
131
|
-
* Read the whole bundle,
|
|
146
|
+
* Read the whole bundle, folding a per-field store into it on the way.
|
|
132
147
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
148
|
+
* The fold is lazy and idempotent: a read that finds no bundle but finds the
|
|
149
|
+
* per-field names merges them into one record, writes it, and removes the old
|
|
150
|
+
* entries. A failed write leaves them in place so the next read tries again —
|
|
151
|
+
* a store is never left with neither layout.
|
|
137
152
|
*
|
|
138
153
|
* @param store - the caller's credential store.
|
|
139
154
|
* @returns the bundle; empty when nothing is stored.
|
|
@@ -182,11 +197,11 @@ export declare function readE2BKey(store: CredentialsStore): E2BSection | undefi
|
|
|
182
197
|
*/
|
|
183
198
|
export declare function updateCredentials(store: CredentialsStore, update: (current: CredentialBundle) => CredentialBundle | undefined): CredentialBundle;
|
|
184
199
|
/**
|
|
185
|
-
* Store the Huawei Cloud pair, replacing
|
|
200
|
+
* Store the Huawei Cloud pair, replacing whatever is there.
|
|
186
201
|
*
|
|
187
202
|
* One write, so the pair can never be left half-replaced. A save without a
|
|
188
|
-
* token removes a
|
|
189
|
-
*
|
|
203
|
+
* token removes a stored one: a token belongs to the credential set that
|
|
204
|
+
* carried it, and inheriting a stale one would sign the next request with
|
|
190
205
|
* someone else's session.
|
|
191
206
|
*
|
|
192
207
|
* @param store - the caller's credential store.
|
|
@@ -211,10 +226,17 @@ export declare function clearE2BKey(store: CredentialsStore): void;
|
|
|
211
226
|
*
|
|
212
227
|
* The whole record goes, so a clear cannot leave a token or a key behind for
|
|
213
228
|
* the next pair to inherit. Deleting an absent entry is a no-op in every
|
|
214
|
-
* backend
|
|
215
|
-
*
|
|
229
|
+
* backend.
|
|
230
|
+
*
|
|
231
|
+
* The per-field names are removed as well, so a store the fold never reached
|
|
232
|
+
* does not keep answering after a clear. Those deletes are best-effort and the
|
|
233
|
+
* main record's is not: a refusal on `CREDENTIAL_ACCOUNT` means the credentials
|
|
234
|
+
* are still there and the person has to be told, while the per-field names are
|
|
235
|
+
* leftovers whose removal nothing depends on. Swallowing the first would report
|
|
236
|
+
* a clear that did not happen.
|
|
216
237
|
*
|
|
217
238
|
* @param store - the caller's credential store.
|
|
239
|
+
* @throws when the backend refused to remove the main record.
|
|
218
240
|
*/
|
|
219
241
|
export declare function clearCredentials(store: CredentialsStore): void;
|
|
220
242
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../src/accounts.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../src/accounts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,kEAAmE,CAAA;AAE/F;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,wBAAwB,CAAA;AAEvD,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,UAAU,CAAA;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAE7D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAoBtE;AAoCD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAOzE;AA8ED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,6BAA6B,CAAA;AA2BxD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,gBAAgB,GAAG,eAAe,GAAG,SAAS,CAQnF;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,UAAU,GAAG,SAAS,CAE1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,gBAAgB,GAAG,SAAS,GAClE,gBAAgB,CAMlB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,GAAG,IAAI,CAS5F;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CAE1E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAMzD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAI9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9C"}
|