@huaweidevtools/aishell-dsh-credentials-store 0.0.1-beta.2 → 0.0.1-beta.4
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/LICENSE +1 -1
- package/README.md +62 -14
- package/lib/accounts.d.ts +187 -0
- package/lib/accounts.d.ts.map +1 -0
- package/lib/accounts.js +298 -0
- package/lib/accounts.js.map +1 -0
- package/lib/index-CsXaUaRH.d.ts +211 -0
- package/lib/index-CsXaUaRH.d.ts.map +1 -0
- package/lib/index.d.ts +3 -208
- package/lib/index.js +26 -28
- package/lib/index.js.map +1 -1
- package/lib/paths-DtFRzop6.js +46 -0
- package/lib/paths-DtFRzop6.js.map +1 -0
- package/lib/paths.d.ts +56 -0
- package/lib/paths.d.ts.map +1 -0
- package/lib/paths.js +3 -0
- package/lib/scope-CFetnbG3.d.ts +71 -0
- package/lib/scope-CFetnbG3.d.ts.map +1 -0
- package/lib/scope-QmrHZaZt.js +96 -0
- package/lib/scope-QmrHZaZt.js.map +1 -0
- package/lib/scope.d.ts +2 -0
- package/lib/scope.js +3 -0
- package/lib/signer-BPR0UAF0.js +66 -0
- package/lib/signer-BPR0UAF0.js.map +1 -0
- package/lib/signer-Et0DIH6o.d.ts +40 -0
- package/lib/signer-Et0DIH6o.d.ts.map +1 -0
- package/lib/signer.d.ts +2 -40
- package/lib/signer.js +2 -65
- package/lib/types/accounts.d.ts +231 -0
- package/lib/types/accounts.d.ts.map +1 -0
- package/lib/types/accounts.js +384 -0
- package/lib/types/accounts.js.map +1 -0
- package/lib/types/backends/fingerprint.d.ts +41 -0
- package/lib/types/backends/fingerprint.d.ts.map +1 -0
- package/lib/types/backends/fingerprint.js +135 -0
- package/lib/types/backends/fingerprint.js.map +1 -0
- package/lib/types/backends/keyring.d.ts +69 -0
- package/lib/types/backends/keyring.d.ts.map +1 -0
- package/lib/types/backends/keyring.js +107 -0
- package/lib/types/backends/keyring.js.map +1 -0
- package/lib/types/backends/memory.d.ts +22 -0
- package/lib/types/backends/memory.d.ts.map +1 -0
- package/lib/types/backends/memory.js +35 -0
- package/lib/types/backends/memory.js.map +1 -0
- package/lib/types/failover.d.ts +136 -0
- package/lib/types/failover.d.ts.map +1 -0
- package/lib/types/failover.js +242 -0
- package/lib/types/failover.js.map +1 -0
- package/lib/types/index.d.ts +70 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +73 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/paths.d.ts +50 -0
- package/lib/types/paths.d.ts.map +1 -0
- package/lib/types/paths.js +59 -0
- package/lib/types/paths.js.map +1 -0
- package/lib/types/scope.d.ts +65 -0
- package/lib/types/scope.d.ts.map +1 -0
- package/lib/types/scope.js +102 -0
- package/lib/types/scope.js.map +1 -0
- package/lib/types/signer.d.ts +36 -0
- package/lib/types/signer.d.ts.map +1 -0
- package/lib/types/signer.js +81 -0
- package/lib/types/signer.js.map +1 -0
- package/lib/types/verify.d.ts +114 -0
- package/lib/types/verify.d.ts.map +1 -0
- package/lib/types/verify.js +240 -0
- package/lib/types/verify.js.map +1 -0
- package/lib/verify.d.ts +72 -0
- package/lib/verify.d.ts.map +1 -0
- package/lib/verify.js +172 -0
- package/lib/verify.js.map +1 -0
- package/package.json +21 -8
- package/lib/index.d.ts.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage abstraction for aishell DSH plugins.
|
|
3
|
+
*
|
|
4
|
+
* Upper layers (aishell-llm's adapter, aishell-credentials' host half)
|
|
5
|
+
* call {@link createCredentialsStore} and get a {@link CredentialsStore} —
|
|
6
|
+
* they never learn which backend is serving. The factory probes backends in
|
|
7
|
+
* priority order (OS keyring first, process memory last) and returns the
|
|
8
|
+
* first that answers. New backends (fingerprint + encrypted file, etc.) slot
|
|
9
|
+
* into the chain without touching callers. A keyring that answers the probe
|
|
10
|
+
* but later rejects writes fails over to the next backend on first failure.
|
|
11
|
+
*
|
|
12
|
+
* This is a tool library, not a DSH plugin — no `dsh.client`, no
|
|
13
|
+
* `cordis.patch.yml`, no `apply`. DSH's own `ctx.credentials` is untouched;
|
|
14
|
+
* user-configured models and `.credentials.yaml` keep working.
|
|
15
|
+
*
|
|
16
|
+
* @module @huaweidevtools/aishell-dsh-credentials-store
|
|
17
|
+
*/
|
|
18
|
+
import { KeyringBackend } from "./backends/keyring.js";
|
|
19
|
+
import { FingerprintBackend } from "./backends/fingerprint.js";
|
|
20
|
+
import { MemoryBackend } from "./backends/memory.js";
|
|
21
|
+
import { FailoverStore } from "./failover.js";
|
|
22
|
+
/**
|
|
23
|
+
* Probe backends in priority order and return the first that answers.
|
|
24
|
+
*
|
|
25
|
+
* Priority: OS keyring (durable, cross-process) → machine-fingerprint
|
|
26
|
+
* encrypted file (durable, same-machine) → process memory (volatile,
|
|
27
|
+
* last resort). A backend that cannot initialize — no D-Bus secret service
|
|
28
|
+
* on a headless Linux box, keychain locked, etc. — is skipped, not fatal.
|
|
29
|
+
*
|
|
30
|
+
* A keyring that passes the construction probe but is later found unwritable
|
|
31
|
+
* (GNOME login keyring locked: reads come back null, writes fail — the probe
|
|
32
|
+
* cannot see this) is wrapped in a {@link FailoverStore}: its first failed
|
|
33
|
+
* write fails the whole process over to the fingerprint backend, so saving
|
|
34
|
+
* credentials works instead of erroring. The wrapper also records which
|
|
35
|
+
* accounts the keyring must not answer for, so a credential saved while the
|
|
36
|
+
* collection was locked is not shadowed by the keyring's stale copy once it
|
|
37
|
+
* unlocks.
|
|
38
|
+
*
|
|
39
|
+
* @returns the highest-priority backend that could initialize.
|
|
40
|
+
*/
|
|
41
|
+
export function createCredentialsStore(config = {}) {
|
|
42
|
+
const keyring = (() => {
|
|
43
|
+
try {
|
|
44
|
+
return new KeyringBackend(config);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// OS keyring unavailable — try the fingerprint file backend.
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
if (keyring !== undefined) {
|
|
52
|
+
// FingerprintBackend construction probes machine-id (deriveKey); if that
|
|
53
|
+
// fails, MemoryBackend closes the chain — createFallback never throws.
|
|
54
|
+
return new FailoverStore(keyring, { createFallback: () => createFallbackStore(config) });
|
|
55
|
+
}
|
|
56
|
+
return createFallbackStore(config);
|
|
57
|
+
}
|
|
58
|
+
/** Probe the durable-file and memory backends behind the keyring. */
|
|
59
|
+
function createFallbackStore(config) {
|
|
60
|
+
try {
|
|
61
|
+
return new FingerprintBackend(config);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Machine fingerprint unavailable (no machine ID) — fall back to memory.
|
|
65
|
+
}
|
|
66
|
+
return new MemoryBackend(config);
|
|
67
|
+
}
|
|
68
|
+
export { CREDENTIAL_SCOPE, CREDENTIALS_FILE_NAME, DEFAULT_SCOPE, KEYRING_SERVICE } from "./scope.js";
|
|
69
|
+
export { KeyringBackend, __setKeyringModule } from "./backends/keyring.js";
|
|
70
|
+
export { FingerprintBackend } from "./backends/fingerprint.js";
|
|
71
|
+
export { MemoryBackend } from "./backends/memory.js";
|
|
72
|
+
export { FailoverStore } from "./failover.js";
|
|
73
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AA+B7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiC,EAAE;IACxE,MAAM,OAAO,GAAiC,CAAC,GAAG,EAAE;QAClD,IAAI,CAAC;YACH,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,CAAA;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;YAC7D,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC,CAAC,EAAE,CAAA;IACJ,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,yEAAyE;QACzE,uEAAuE;QACvE,OAAO,IAAI,aAAa,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC1F,CAAC;IACD,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAA;AACpC,CAAC;AAED,qEAAqE;AACrE,SAAS,mBAAmB,CAAC,MAA8B;IACzD,IAAI,CAAC;QACH,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAA;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;IACD,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC,CAAA;AAClC,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACpG,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where per-host state lives. One definition, shared.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this lives in the store package
|
|
5
|
+
*
|
|
6
|
+
* `DSH_HOME` is the host's data directory, and two things in this repo derive
|
|
7
|
+
* from it: the encrypted credentials file here, and the terraform cache in
|
|
8
|
+
* `aishell-deploy`. Defining it twice would let the two drift — the same
|
|
9
|
+
* failure shape the credential-scope constant was created to end.
|
|
10
|
+
*
|
|
11
|
+
* The direction is forced by the dependency graph: `aishell-deploy` already
|
|
12
|
+
* depends on this package, so a definition here is reachable from there, while
|
|
13
|
+
* the reverse would be a cycle. This module is the lower of the two, so it is
|
|
14
|
+
* where the definition belongs.
|
|
15
|
+
*
|
|
16
|
+
* @module @huaweidevtools/aishell-dsh-credentials-store/paths
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The fallback data directory when the host sets no `DSH_HOME`.
|
|
20
|
+
*
|
|
21
|
+
* `~/.aishell` — the same directory the desktop app uses, so a library or
|
|
22
|
+
* headless run shares one location with a real install rather than inventing a
|
|
23
|
+
* second home directory.
|
|
24
|
+
*/
|
|
25
|
+
export declare const FALLBACK_HOME_NAME = ".aishell";
|
|
26
|
+
/**
|
|
27
|
+
* The host's data directory.
|
|
28
|
+
*
|
|
29
|
+
* `DSH_HOME` when the host set it — the desktop app and the CLI both do, so
|
|
30
|
+
* this lands where the rest of that host's data lives — otherwise
|
|
31
|
+
* `~/.aishell`.
|
|
32
|
+
*
|
|
33
|
+
* Read per call, never captured: a host that sets `DSH_HOME` after this module
|
|
34
|
+
* is first imported must still be honoured.
|
|
35
|
+
*
|
|
36
|
+
* @returns the base directory; not created here.
|
|
37
|
+
*/
|
|
38
|
+
export declare function dshHome(): string;
|
|
39
|
+
/**
|
|
40
|
+
* The directory holding the encrypted credentials file.
|
|
41
|
+
*
|
|
42
|
+
* `<DSH_HOME>/credentials`. Builds are separated by the *file name* (the
|
|
43
|
+
* credential scope — `plugins.credentials.enc` by default), not by a second
|
|
44
|
+
* directory, so two builds sit side by side here without reading each other's
|
|
45
|
+
* secrets.
|
|
46
|
+
*
|
|
47
|
+
* @returns the directory; not created here.
|
|
48
|
+
*/
|
|
49
|
+
export declare function credentialsDir(): string;
|
|
50
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,aAAa,CAAA;AAE5C;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,IAAI,MAAM,CAIhC;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where per-host state lives. One definition, shared.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this lives in the store package
|
|
5
|
+
*
|
|
6
|
+
* `DSH_HOME` is the host's data directory, and two things in this repo derive
|
|
7
|
+
* from it: the encrypted credentials file here, and the terraform cache in
|
|
8
|
+
* `aishell-deploy`. Defining it twice would let the two drift — the same
|
|
9
|
+
* failure shape the credential-scope constant was created to end.
|
|
10
|
+
*
|
|
11
|
+
* The direction is forced by the dependency graph: `aishell-deploy` already
|
|
12
|
+
* depends on this package, so a definition here is reachable from there, while
|
|
13
|
+
* the reverse would be a cycle. This module is the lower of the two, so it is
|
|
14
|
+
* where the definition belongs.
|
|
15
|
+
*
|
|
16
|
+
* @module @huaweidevtools/aishell-dsh-credentials-store/paths
|
|
17
|
+
*/
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
/**
|
|
21
|
+
* The fallback data directory when the host sets no `DSH_HOME`.
|
|
22
|
+
*
|
|
23
|
+
* `~/.aishell` — the same directory the desktop app uses, so a library or
|
|
24
|
+
* headless run shares one location with a real install rather than inventing a
|
|
25
|
+
* second home directory.
|
|
26
|
+
*/
|
|
27
|
+
export const FALLBACK_HOME_NAME = '.aishell';
|
|
28
|
+
/**
|
|
29
|
+
* The host's data directory.
|
|
30
|
+
*
|
|
31
|
+
* `DSH_HOME` when the host set it — the desktop app and the CLI both do, so
|
|
32
|
+
* this lands where the rest of that host's data lives — otherwise
|
|
33
|
+
* `~/.aishell`.
|
|
34
|
+
*
|
|
35
|
+
* Read per call, never captured: a host that sets `DSH_HOME` after this module
|
|
36
|
+
* is first imported must still be honoured.
|
|
37
|
+
*
|
|
38
|
+
* @returns the base directory; not created here.
|
|
39
|
+
*/
|
|
40
|
+
export function dshHome() {
|
|
41
|
+
const configured = process.env.DSH_HOME;
|
|
42
|
+
if (configured !== undefined && configured.trim() !== '')
|
|
43
|
+
return configured;
|
|
44
|
+
return join(homedir(), FALLBACK_HOME_NAME);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The directory holding the encrypted credentials file.
|
|
48
|
+
*
|
|
49
|
+
* `<DSH_HOME>/credentials`. Builds are separated by the *file name* (the
|
|
50
|
+
* credential scope — `plugins.credentials.enc` by default), not by a second
|
|
51
|
+
* directory, so two builds sit side by side here without reading each other's
|
|
52
|
+
* secrets.
|
|
53
|
+
*
|
|
54
|
+
* @returns the directory; not created here.
|
|
55
|
+
*/
|
|
56
|
+
export function credentialsDir() {
|
|
57
|
+
return join(dshHome(), 'credentials');
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAA;AAE5C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAA;IACvC,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,UAAU,CAAA;IAC3E,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,kBAAkB,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAA;AACvC,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which credential namespace this build uses.
|
|
3
|
+
*
|
|
4
|
+
* ## The knob
|
|
5
|
+
*
|
|
6
|
+
* A build-time constant, not a runtime setting:
|
|
7
|
+
*
|
|
8
|
+
* ```sh
|
|
9
|
+
* AISHELL_CREDENTIAL_SCOPE=staging pnpm run build
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Default `plugins`. It decides two things, and it decides both at once —
|
|
13
|
+
* which is the point:
|
|
14
|
+
*
|
|
15
|
+
* | Used as | Default |
|
|
16
|
+
* |---|---|
|
|
17
|
+
* | keyring service name | `aishell-dsh-plugins` |
|
|
18
|
+
* | encrypted file name | `plugins.credentials.enc` |
|
|
19
|
+
*
|
|
20
|
+
* ## Why it is not a plugin config option
|
|
21
|
+
*
|
|
22
|
+
* It was one, briefly: `keyringService` sat on four plugins' `Config`. Only one
|
|
23
|
+
* of the four writes and all four read, so a deployment that set it on a reader
|
|
24
|
+
* but not the writer stored credentials in one namespace and looked for them in
|
|
25
|
+
* another — the form said "saved" while every tool said "not authenticated".
|
|
26
|
+
* A knob whose only observable effect is that failure is not worth having.
|
|
27
|
+
*
|
|
28
|
+
* A constant cannot be set on one side only. Two deployments that must not see
|
|
29
|
+
* each other's credentials (a staging profile beside a production one) differ
|
|
30
|
+
* by *building* differently, which is the honest place for a persistence
|
|
31
|
+
* boundary.
|
|
32
|
+
*
|
|
33
|
+
* ## Why there is no migration
|
|
34
|
+
*
|
|
35
|
+
* The default changed from `aishell-dsh` to `aishell-dsh-plugins` before the
|
|
36
|
+
* first release, so no install ever wrote the old namespace. Nothing reads it,
|
|
37
|
+
* and adding a migration path would mean keeping the old name alive forever for
|
|
38
|
+
* a state that never existed.
|
|
39
|
+
*
|
|
40
|
+
* @module @huaweidevtools/aishell-dsh-credentials-store/scope
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* The default scope.
|
|
44
|
+
*
|
|
45
|
+
* Named for the repository, not for a feature: the credential store is shared
|
|
46
|
+
* by every plugin in this set, so it is not `llm`'s or `e2b`'s.
|
|
47
|
+
*/
|
|
48
|
+
export declare const DEFAULT_SCOPE = "plugins";
|
|
49
|
+
/** The scope this build uses. */
|
|
50
|
+
export declare const CREDENTIAL_SCOPE: string;
|
|
51
|
+
/**
|
|
52
|
+
* The keyring service every entry of this build lives under.
|
|
53
|
+
*
|
|
54
|
+
* Part of the persistence contract: renaming it orphans every stored entry
|
|
55
|
+
* while looking like a harmless constant change.
|
|
56
|
+
*/
|
|
57
|
+
export declare const KEYRING_SERVICE: string;
|
|
58
|
+
/**
|
|
59
|
+
* The encrypted file name for this build.
|
|
60
|
+
*
|
|
61
|
+
* Scoped the same way as the keyring service, so two builds on one machine
|
|
62
|
+
* cannot collide in either backend.
|
|
63
|
+
*/
|
|
64
|
+
export declare const CREDENTIALS_FILE_NAME: string;
|
|
65
|
+
//# sourceMappingURL=scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,aAAa,YAAY,CAAA;AAkDtC,iCAAiC;AACjC,eAAO,MAAM,gBAAgB,QAAiB,CAAA;AAE9C;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAoC,CAAA;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAwC,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which credential namespace this build uses.
|
|
3
|
+
*
|
|
4
|
+
* ## The knob
|
|
5
|
+
*
|
|
6
|
+
* A build-time constant, not a runtime setting:
|
|
7
|
+
*
|
|
8
|
+
* ```sh
|
|
9
|
+
* AISHELL_CREDENTIAL_SCOPE=staging pnpm run build
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Default `plugins`. It decides two things, and it decides both at once —
|
|
13
|
+
* which is the point:
|
|
14
|
+
*
|
|
15
|
+
* | Used as | Default |
|
|
16
|
+
* |---|---|
|
|
17
|
+
* | keyring service name | `aishell-dsh-plugins` |
|
|
18
|
+
* | encrypted file name | `plugins.credentials.enc` |
|
|
19
|
+
*
|
|
20
|
+
* ## Why it is not a plugin config option
|
|
21
|
+
*
|
|
22
|
+
* It was one, briefly: `keyringService` sat on four plugins' `Config`. Only one
|
|
23
|
+
* of the four writes and all four read, so a deployment that set it on a reader
|
|
24
|
+
* but not the writer stored credentials in one namespace and looked for them in
|
|
25
|
+
* another — the form said "saved" while every tool said "not authenticated".
|
|
26
|
+
* A knob whose only observable effect is that failure is not worth having.
|
|
27
|
+
*
|
|
28
|
+
* A constant cannot be set on one side only. Two deployments that must not see
|
|
29
|
+
* each other's credentials (a staging profile beside a production one) differ
|
|
30
|
+
* by *building* differently, which is the honest place for a persistence
|
|
31
|
+
* boundary.
|
|
32
|
+
*
|
|
33
|
+
* ## Why there is no migration
|
|
34
|
+
*
|
|
35
|
+
* The default changed from `aishell-dsh` to `aishell-dsh-plugins` before the
|
|
36
|
+
* first release, so no install ever wrote the old namespace. Nothing reads it,
|
|
37
|
+
* and adding a migration path would mean keeping the old name alive forever for
|
|
38
|
+
* a state that never existed.
|
|
39
|
+
*
|
|
40
|
+
* @module @huaweidevtools/aishell-dsh-credentials-store/scope
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* The default scope.
|
|
44
|
+
*
|
|
45
|
+
* Named for the repository, not for a feature: the credential store is shared
|
|
46
|
+
* by every plugin in this set, so it is not `llm`'s or `e2b`'s.
|
|
47
|
+
*/
|
|
48
|
+
export const DEFAULT_SCOPE = 'plugins';
|
|
49
|
+
/**
|
|
50
|
+
* Accepted scope shape: a lowercase identifier.
|
|
51
|
+
*
|
|
52
|
+
* The value reaches a keyring service name and a file name, so it is validated
|
|
53
|
+
* as a strict identifier rather than merely "not obviously a path": no
|
|
54
|
+
* separators, no dots, no leading dash. Assuming a value is benign because the
|
|
55
|
+
* caller is trusted is how path traversal gets in.
|
|
56
|
+
*/
|
|
57
|
+
const SCOPE_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the scope for this build.
|
|
60
|
+
*
|
|
61
|
+
* Fails loudly on a malformed value rather than falling back to the default:
|
|
62
|
+
* a typo would otherwise silently share the namespace with every unset build,
|
|
63
|
+
* which is exactly the confusion scoping exists to avoid.
|
|
64
|
+
*
|
|
65
|
+
* @returns the validated scope.
|
|
66
|
+
* @throws when the injected value is not a valid scope.
|
|
67
|
+
*/
|
|
68
|
+
function resolveScope() {
|
|
69
|
+
// `typeof` rather than a direct read: an undeclared identifier throws, and a
|
|
70
|
+
// source run legitimately has no injected value.
|
|
71
|
+
const injected = typeof __AISHELL_CREDENTIAL_SCOPE__ === 'string'
|
|
72
|
+
? __AISHELL_CREDENTIAL_SCOPE__
|
|
73
|
+
: undefined;
|
|
74
|
+
if (injected === undefined || injected === '')
|
|
75
|
+
return DEFAULT_SCOPE;
|
|
76
|
+
const normalized = injected.trim().toLowerCase();
|
|
77
|
+
if (normalized.length === 0)
|
|
78
|
+
return DEFAULT_SCOPE;
|
|
79
|
+
if (!SCOPE_PATTERN.test(normalized)) {
|
|
80
|
+
throw new Error(`AISHELL_CREDENTIAL_SCOPE=${JSON.stringify(injected)} is not a valid credential scope: `
|
|
81
|
+
+ `expected 1-64 characters of [a-z0-9_-], starting with a letter or digit `
|
|
82
|
+
+ '(for example "plugins" or "staging").');
|
|
83
|
+
}
|
|
84
|
+
return normalized;
|
|
85
|
+
}
|
|
86
|
+
/** The scope this build uses. */
|
|
87
|
+
export const CREDENTIAL_SCOPE = resolveScope();
|
|
88
|
+
/**
|
|
89
|
+
* The keyring service every entry of this build lives under.
|
|
90
|
+
*
|
|
91
|
+
* Part of the persistence contract: renaming it orphans every stored entry
|
|
92
|
+
* while looking like a harmless constant change.
|
|
93
|
+
*/
|
|
94
|
+
export const KEYRING_SERVICE = `aishell-dsh-${CREDENTIAL_SCOPE}`;
|
|
95
|
+
/**
|
|
96
|
+
* The encrypted file name for this build.
|
|
97
|
+
*
|
|
98
|
+
* Scoped the same way as the keyring service, so two builds on one machine
|
|
99
|
+
* cannot collide in either backend.
|
|
100
|
+
*/
|
|
101
|
+
export const CREDENTIALS_FILE_NAME = `${CREDENTIAL_SCOPE}.credentials.enc`;
|
|
102
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAA;AAWtC;;;;;;;GAOG;AACH,MAAM,aAAa,GAAG,4BAA4B,CAAA;AAElD;;;;;;;;;GASG;AACH,SAAS,YAAY;IACnB,6EAA6E;IAC7E,iDAAiD;IACjD,MAAM,QAAQ,GAAG,OAAO,4BAA4B,KAAK,QAAQ;QAC/D,CAAC,CAAC,4BAA4B;QAC9B,CAAC,CAAC,SAAS,CAAA;IACb,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE;QAAE,OAAO,aAAa,CAAA;IACnE,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAChD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,aAAa,CAAA;IACjD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,oCAAoC;cACtF,0EAA0E;cAC1E,uCAAuC,CAC1C,CAAA;IACH,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,iCAAiC;AACjC,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,EAAE,CAAA;AAE9C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,gBAAgB,EAAE,CAAA;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,gBAAgB,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Huawei Cloud SDK-HMAC-SHA256 request signing.
|
|
3
|
+
*
|
|
4
|
+
* Signs an HTTP request with AK/SK (and an optional temporary SecurityToken)
|
|
5
|
+
* using the same algorithm as the official Huawei Cloud SDK. Produces the
|
|
6
|
+
* `Authorization`, `x-sdk-date`, and `x-security-token` headers to attach
|
|
7
|
+
* to a `fetch` call.
|
|
8
|
+
*
|
|
9
|
+
* @module @huaweidevtools/aishell-dsh-credentials-store/signer
|
|
10
|
+
*/
|
|
11
|
+
/** Credentials for signing. */
|
|
12
|
+
export interface SignCredentials {
|
|
13
|
+
readonly ak: string;
|
|
14
|
+
readonly sk: string;
|
|
15
|
+
readonly securityToken?: string;
|
|
16
|
+
}
|
|
17
|
+
/** The headers produced by {@link sign}. */
|
|
18
|
+
export interface SignedHeaders {
|
|
19
|
+
readonly host: string;
|
|
20
|
+
readonly 'x-sdk-date': string;
|
|
21
|
+
readonly Authorization: string;
|
|
22
|
+
readonly 'x-security-token'?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sign an HTTP request with SDK-HMAC-SHA256.
|
|
26
|
+
*
|
|
27
|
+
* @param method - HTTP method (GET, POST, ...).
|
|
28
|
+
* @param host - Host component of the request URL (e.g. `openai.aishell.openbuildlab.com`).
|
|
29
|
+
* @param path - Path component of the request URL (e.g. `/chat/completions`).
|
|
30
|
+
* @param query - Query parameters as a key-value map.
|
|
31
|
+
* @param body - Request body (stringified for hashing; empty string for GET).
|
|
32
|
+
* @param credentials - AK/SK and optional SecurityToken.
|
|
33
|
+
* @returns The headers to attach to the fetch call.
|
|
34
|
+
*/
|
|
35
|
+
export declare function sign(method: string, host: string, path: string, query: Record<string, string>, body: string, credentials: SignCredentials): SignedHeaders;
|
|
36
|
+
//# sourceMappingURL=signer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,+BAA+B;AAC/B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CACrC;AAiCD;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,eAAe,GAC3B,aAAa,CAoCf"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Huawei Cloud SDK-HMAC-SHA256 request signing.
|
|
3
|
+
*
|
|
4
|
+
* Signs an HTTP request with AK/SK (and an optional temporary SecurityToken)
|
|
5
|
+
* using the same algorithm as the official Huawei Cloud SDK. Produces the
|
|
6
|
+
* `Authorization`, `x-sdk-date`, and `x-security-token` headers to attach
|
|
7
|
+
* to a `fetch` call.
|
|
8
|
+
*
|
|
9
|
+
* @module @huaweidevtools/aishell-dsh-credentials-store/signer
|
|
10
|
+
*/
|
|
11
|
+
import crypto from 'node:crypto';
|
|
12
|
+
function sha256Hex(data) {
|
|
13
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
14
|
+
}
|
|
15
|
+
function hmacSha256(key, data) {
|
|
16
|
+
return crypto.createHmac('sha256', key).update(data).digest('hex');
|
|
17
|
+
}
|
|
18
|
+
/** RFC 3986 URL-encode a string (unreserved chars stay literal). */
|
|
19
|
+
function urlEncode(str) {
|
|
20
|
+
const hex = (c) => '%' + (c < 16 ? '0' : '') + c.toString(16).toUpperCase();
|
|
21
|
+
const noEscape = new Set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split(''));
|
|
22
|
+
let out = '';
|
|
23
|
+
for (const ch of str) {
|
|
24
|
+
const c = ch.codePointAt(0);
|
|
25
|
+
if (c < 0x80) {
|
|
26
|
+
out += noEscape.has(ch) ? ch : hex(c);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
out += encodeURIComponent(ch);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
/** ISO 8601 timestamp in Huawei Cloud format: `20260912T064552Z`. */
|
|
35
|
+
function sdkTimestamp() {
|
|
36
|
+
// toISOString() returns "2026-09-12T06:45:52.123Z"; strip separators and
|
|
37
|
+
// milliseconds but keep the trailing Z — do NOT append another one.
|
|
38
|
+
return new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+/, '');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Sign an HTTP request with SDK-HMAC-SHA256.
|
|
42
|
+
*
|
|
43
|
+
* @param method - HTTP method (GET, POST, ...).
|
|
44
|
+
* @param host - Host component of the request URL (e.g. `openai.aishell.openbuildlab.com`).
|
|
45
|
+
* @param path - Path component of the request URL (e.g. `/chat/completions`).
|
|
46
|
+
* @param query - Query parameters as a key-value map.
|
|
47
|
+
* @param body - Request body (stringified for hashing; empty string for GET).
|
|
48
|
+
* @param credentials - AK/SK and optional SecurityToken.
|
|
49
|
+
* @returns The headers to attach to the fetch call.
|
|
50
|
+
*/
|
|
51
|
+
export function sign(method, host, path, query, body, credentials) {
|
|
52
|
+
const ts = sdkTimestamp();
|
|
53
|
+
// Canonical query string (sorted keys)
|
|
54
|
+
const cqs = Object.entries(query)
|
|
55
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
56
|
+
.map(([k, v]) => `${urlEncode(k)}=${urlEncode(v)}`)
|
|
57
|
+
.join('&');
|
|
58
|
+
// Canonical URI: path segments URL-encoded, joined with /, trailing /
|
|
59
|
+
const curi = '/' + path.split('/').filter(Boolean).map(urlEncode).join('/') + '/';
|
|
60
|
+
// Signed headers
|
|
61
|
+
const hasToken = credentials.securityToken !== undefined;
|
|
62
|
+
const signedHeaders = hasToken ? 'host;x-sdk-date;x-security-token' : 'host;x-sdk-date';
|
|
63
|
+
const canonicalHeaders = hasToken
|
|
64
|
+
? `host:${host}\nx-sdk-date:${ts}\nx-security-token:${credentials.securityToken}\n`
|
|
65
|
+
: `host:${host}\nx-sdk-date:${ts}\n`;
|
|
66
|
+
// Payload hash
|
|
67
|
+
const payloadHash = sha256Hex(body);
|
|
68
|
+
// Canonical request
|
|
69
|
+
const canonicalRequest = [method, curi, cqs, canonicalHeaders, signedHeaders, payloadHash].join('\n');
|
|
70
|
+
// String to sign
|
|
71
|
+
const stringToSign = `SDK-HMAC-SHA256\n${ts}\n${sha256Hex(canonicalRequest)}`;
|
|
72
|
+
const signature = hmacSha256(credentials.sk, stringToSign);
|
|
73
|
+
const headers = {
|
|
74
|
+
host,
|
|
75
|
+
'x-sdk-date': ts,
|
|
76
|
+
Authorization: `SDK-HMAC-SHA256 Access=${credentials.ak}, SignedHeaders=${signedHeaders}, Signature=${signature}`,
|
|
77
|
+
...(hasToken ? { 'x-security-token': credentials.securityToken } : {}),
|
|
78
|
+
};
|
|
79
|
+
return headers;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/signer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Huawei Cloud IAM credential verification — one implementation, shared.
|
|
3
|
+
*
|
|
4
|
+
* ## Why it moved here
|
|
5
|
+
*
|
|
6
|
+
* Two packages verified credentials independently: `aishell-credentials`' Host
|
|
7
|
+
* route, with `GET /v3/auth/domains` (it only wanted the account name), and
|
|
8
|
+
* the copied ops tool `auth`, with `GET /v3/projects` (it also wanted the
|
|
9
|
+
* account id and the project list). Same question — "are these credentials
|
|
10
|
+
* valid?" — two endpoints, two error-message vocabularies, two chances to
|
|
11
|
+
* disagree about what a 403 means.
|
|
12
|
+
*
|
|
13
|
+
* This module is the one answer. It is storage-adjacent rather than
|
|
14
|
+
* credential-form-specific, so it lives with the store and the signer, and
|
|
15
|
+
* every caller gets the same verdict in the same shape.
|
|
16
|
+
*
|
|
17
|
+
* ## Why `/v3/projects` is the endpoint
|
|
18
|
+
*
|
|
19
|
+
* It is a superset of the domains call:
|
|
20
|
+
*
|
|
21
|
+
* - A 200 proves the AK/SK (and STS token) work, signed exactly the way every
|
|
22
|
+
* other call is.
|
|
23
|
+
* - Each project carries `domain_id`, which **is** the account id (Huawei
|
|
24
|
+
* Cloud's "domain" and "account" are the same object).
|
|
25
|
+
* - Each project's `name` is a region name, so a caller that needs a
|
|
26
|
+
* `project_id` for a region-scoped API has it without a second request.
|
|
27
|
+
*
|
|
28
|
+
* The account *name* is the one thing projects does not carry, so it is read
|
|
29
|
+
* from `/v3/auth/domains` **best-effort** — some AK/SK scopes do not grant it,
|
|
30
|
+
* and a missing name is diagnostic-only. A failure there never fails
|
|
31
|
+
* verification.
|
|
32
|
+
*
|
|
33
|
+
* ## Region semantics, which surprise people
|
|
34
|
+
*
|
|
35
|
+
* IAM is a global service. The region in the URL only routes to a regional
|
|
36
|
+
* replica; it does not scope the result. So a wrong region still answers 200
|
|
37
|
+
* with the same account, and the real mismatch surfaces at the first
|
|
38
|
+
* region-scoped call. Verification therefore validates the credentials and
|
|
39
|
+
* **not** the region — conflating them would report "invalid credentials" for
|
|
40
|
+
* a typo'd region, sending the person to check the wrong thing.
|
|
41
|
+
*
|
|
42
|
+
* ## Result shape
|
|
43
|
+
*
|
|
44
|
+
* A discriminated union rather than a throw: a rejected credential is an
|
|
45
|
+
* expected outcome of asking someone for credentials, not an exception. The
|
|
46
|
+
* caller decides whether to show, log or retry it.
|
|
47
|
+
*
|
|
48
|
+
* @module @huaweidevtools/aishell-dsh-credentials-store/verify
|
|
49
|
+
*/
|
|
50
|
+
import { type SignCredentials } from './signer.ts';
|
|
51
|
+
/** One project visible to the credentials; its `name` is a region name. */
|
|
52
|
+
export interface VerifiedProject {
|
|
53
|
+
/** The `project_id` a region-scoped API takes. */
|
|
54
|
+
readonly id: string;
|
|
55
|
+
/** The project name — the region name, e.g. `cn-north-4`. */
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly description?: string;
|
|
58
|
+
}
|
|
59
|
+
/** The account the credentials belong to. */
|
|
60
|
+
export interface VerifiedAccount {
|
|
61
|
+
/** The account id, equal to IAM's `domain_id`. */
|
|
62
|
+
readonly accountId: string;
|
|
63
|
+
/** The account (domain) name; falls back to {@link accountId} when unreadable. */
|
|
64
|
+
readonly name: string;
|
|
65
|
+
/**
|
|
66
|
+
* Every project visible to these credentials.
|
|
67
|
+
*
|
|
68
|
+
* More than one project may share a region name (enterprise projects), which
|
|
69
|
+
* is why this is not reduced to a region→project map: picking one would be a
|
|
70
|
+
* deployment decision this module has no basis for.
|
|
71
|
+
*/
|
|
72
|
+
readonly projects: readonly VerifiedProject[];
|
|
73
|
+
}
|
|
74
|
+
/** A successful verification. */
|
|
75
|
+
export interface VerifyOk {
|
|
76
|
+
readonly ok: true;
|
|
77
|
+
readonly account: VerifiedAccount;
|
|
78
|
+
/** The region the call was routed through; see the module note — not verified. */
|
|
79
|
+
readonly region: string;
|
|
80
|
+
/** Whether the credentials were temporary (STS). */
|
|
81
|
+
readonly sts: boolean;
|
|
82
|
+
}
|
|
83
|
+
/** A failed verification. */
|
|
84
|
+
export interface VerifyFailed {
|
|
85
|
+
readonly ok: false;
|
|
86
|
+
/** HTTP status from IAM, or `0` for a network/signing failure. */
|
|
87
|
+
readonly status: number;
|
|
88
|
+
/** A sentence the person or model can act on. */
|
|
89
|
+
readonly message: string;
|
|
90
|
+
}
|
|
91
|
+
/** The outcome of one verification. */
|
|
92
|
+
export type VerifyResult = VerifyOk | VerifyFailed;
|
|
93
|
+
/** Options for {@link verifyCredentials}. */
|
|
94
|
+
export interface VerifyOptions {
|
|
95
|
+
/** Region used to route the call. Does not scope the result; see the module note. */
|
|
96
|
+
readonly region: string;
|
|
97
|
+
/** Fetch implementation; defaults to the global. Injected so tests need no network. */
|
|
98
|
+
readonly fetchImpl?: typeof fetch;
|
|
99
|
+
/** Per-request timeout in milliseconds; defaults to 15 s. */
|
|
100
|
+
readonly timeoutMs?: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Verify a credential pair against Huawei Cloud IAM.
|
|
104
|
+
*
|
|
105
|
+
* The pair is signed exactly as every other request is, so a 200 proves the
|
|
106
|
+
* same thing a successful tool call would prove — no separate verification
|
|
107
|
+
* scheme that could drift from the one that actually runs.
|
|
108
|
+
*
|
|
109
|
+
* @param credentials - the AK/SK (and optional STS token) to verify.
|
|
110
|
+
* @param options - region and transport details.
|
|
111
|
+
* @returns the account on success, or a failure naming what IAM said.
|
|
112
|
+
*/
|
|
113
|
+
export declare function verifyCredentials(credentials: SignCredentials, options: VerifyOptions): Promise<VerifyResult>;
|
|
114
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAAQ,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAExD,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAA;CAC9C;AAED,iCAAiC;AACjC,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;IACjB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAA;IACjC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,oDAAoD;IACpD,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;CACtB;AAED,6BAA6B;AAC7B,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAA;IAClB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,uCAAuC;AACvC,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAA;AAElD,6CAA6C;AAC7C,MAAM,WAAW,aAAa;IAC5B,qFAAqF;IACrF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,uFAAuF;IACvF,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AA8CD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,eAAe,EAC5B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAqDvB"}
|