@cortexkit/antigravity-auth-core 2.2.1 → 3.0.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/README.md +24 -2
- package/dist/abort-forwarding.d.ts +3 -0
- package/dist/abort-forwarding.d.ts.map +1 -0
- package/dist/abort-forwarding.js +19 -0
- package/dist/abort-forwarding.js.map +1 -0
- package/dist/account-identity.d.ts +52 -0
- package/dist/account-identity.d.ts.map +1 -0
- package/dist/account-identity.js +66 -0
- package/dist/account-identity.js.map +1 -0
- package/dist/account-manager.d.ts +228 -43
- package/dist/account-manager.d.ts.map +1 -1
- package/dist/account-manager.js +942 -903
- package/dist/account-manager.js.map +1 -1
- package/dist/account-migration.d.ts +309 -0
- package/dist/account-migration.d.ts.map +1 -0
- package/dist/account-migration.js +2284 -0
- package/dist/account-migration.js.map +1 -0
- package/dist/account-repository-codecs.d.ts +82 -0
- package/dist/account-repository-codecs.d.ts.map +1 -0
- package/dist/account-repository-codecs.js +609 -0
- package/dist/account-repository-codecs.js.map +1 -0
- package/dist/account-repository-types.d.ts +831 -0
- package/dist/account-repository-types.d.ts.map +1 -0
- package/dist/account-repository-types.js +137 -0
- package/dist/account-repository-types.js.map +1 -0
- package/dist/account-repository.d.ts +442 -0
- package/dist/account-repository.d.ts.map +1 -0
- package/dist/account-repository.js +2253 -0
- package/dist/account-repository.js.map +1 -0
- package/dist/account-selector.d.ts +470 -0
- package/dist/account-selector.d.ts.map +1 -0
- package/dist/account-selector.js +1383 -0
- package/dist/account-selector.js.map +1 -0
- package/dist/account-storage.d.ts.map +1 -1
- package/dist/account-storage.js +5 -0
- package/dist/account-storage.js.map +1 -1
- package/dist/agy-request-metadata.d.ts.map +1 -1
- package/dist/agy-request-metadata.js +8 -21
- package/dist/agy-request-metadata.js.map +1 -1
- package/dist/agy-transport.d.ts.map +1 -1
- package/dist/agy-transport.js +21 -35
- package/dist/agy-transport.js.map +1 -1
- package/dist/antigravity-command-menu.d.ts +171 -0
- package/dist/antigravity-command-menu.d.ts.map +1 -0
- package/dist/antigravity-command-menu.js +523 -0
- package/dist/antigravity-command-menu.js.map +1 -0
- package/dist/atomic-write.d.ts +18 -1
- package/dist/atomic-write.d.ts.map +1 -1
- package/dist/atomic-write.js +15 -3
- package/dist/atomic-write.js.map +1 -1
- package/dist/common-auth-embedded/NOTICE.txt +21 -0
- package/dist/common-auth-embedded/auth-menu/accounts.d.ts +97 -0
- package/dist/common-auth-embedded/auth-menu/accounts.js +387 -0
- package/dist/common-auth-embedded/auth-menu/ansi.d.ts +26 -0
- package/dist/common-auth-embedded/auth-menu/ansi.js +98 -0
- package/dist/common-auth-embedded/auth-menu/confirm.d.ts +7 -0
- package/dist/common-auth-embedded/auth-menu/confirm.js +21 -0
- package/dist/common-auth-embedded/auth-menu/doctor.d.ts +52 -0
- package/dist/common-auth-embedded/auth-menu/doctor.js +81 -0
- package/dist/common-auth-embedded/auth-menu/index.d.ts +17 -0
- package/dist/common-auth-embedded/auth-menu/index.js +12 -0
- package/dist/common-auth-embedded/auth-menu/login.d.ts +55 -0
- package/dist/common-auth-embedded/auth-menu/login.js +83 -0
- package/dist/common-auth-embedded/auth-menu/menu.d.ts +59 -0
- package/dist/common-auth-embedded/auth-menu/menu.js +73 -0
- package/dist/common-auth-embedded/auth-menu/opencode-v1.d.ts +49 -0
- package/dist/common-auth-embedded/auth-menu/opencode-v1.js +47 -0
- package/dist/common-auth-embedded/auth-menu/select.d.ts +21 -0
- package/dist/common-auth-embedded/auth-menu/select.js +159 -0
- package/dist/common-auth-embedded/auth-menu/terminal.d.ts +43 -0
- package/dist/common-auth-embedded/auth-menu/terminal.js +19 -0
- package/dist/common-auth-embedded/claustrum/consumer.d.ts +102 -0
- package/dist/common-auth-embedded/claustrum/consumer.js +298 -0
- package/dist/common-auth-embedded/claustrum/custody.d.ts +175 -0
- package/dist/common-auth-embedded/claustrum/custody.js +354 -0
- package/dist/common-auth-embedded/claustrum/enrollment.d.ts +158 -0
- package/dist/common-auth-embedded/claustrum/enrollment.js +759 -0
- package/dist/common-auth-embedded/claustrum/errors.d.ts +16 -0
- package/dist/common-auth-embedded/claustrum/errors.js +8 -0
- package/dist/common-auth-embedded/claustrum/host-slot.d.ts +39 -0
- package/dist/common-auth-embedded/claustrum/host-slot.js +72 -0
- package/dist/common-auth-embedded/claustrum/index.d.ts +18 -0
- package/dist/common-auth-embedded/claustrum/index.js +22 -0
- package/dist/common-auth-embedded/claustrum/interlock.d.ts +27 -0
- package/dist/common-auth-embedded/claustrum/interlock.js +29 -0
- package/dist/common-auth-embedded/claustrum/roster.d.ts +208 -0
- package/dist/common-auth-embedded/claustrum/roster.js +536 -0
- package/dist/common-auth-embedded/commands/builtins.d.ts +71 -0
- package/dist/common-auth-embedded/commands/builtins.js +534 -0
- package/dist/common-auth-embedded/commands/index.d.ts +10 -0
- package/dist/common-auth-embedded/commands/index.js +5 -0
- package/dist/common-auth-embedded/commands/menu.d.ts +61 -0
- package/dist/common-auth-embedded/commands/menu.js +300 -0
- package/dist/common-auth-embedded/commands/model.d.ts +238 -0
- package/dist/common-auth-embedded/commands/model.js +16 -0
- package/dist/common-auth-embedded/commands/pi.d.ts +23 -0
- package/dist/common-auth-embedded/commands/pi.js +206 -0
- package/dist/common-auth-embedded/commands/seam.d.ts +72 -0
- package/dist/common-auth-embedded/commands/seam.js +301 -0
- package/dist/common-auth-embedded/fs/atomic-write.d.ts +20 -0
- package/dist/common-auth-embedded/fs/atomic-write.js +80 -0
- package/dist/common-auth-embedded/fs/index.d.ts +7 -0
- package/dist/common-auth-embedded/fs/index.js +6 -0
- package/dist/common-auth-embedded/fs/lock-constants.d.ts +14 -0
- package/dist/common-auth-embedded/fs/lock-constants.js +14 -0
- package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +33 -0
- package/dist/common-auth-embedded/fs/refresh-file-lock.js +501 -0
- package/dist/common-auth-embedded/fs/with-lock.d.ts +33 -0
- package/dist/common-auth-embedded/fs/with-lock.js +48 -0
- package/dist/common-auth-embedded/logger/capture-sink.d.ts +14 -0
- package/dist/common-auth-embedded/logger/capture-sink.js +13 -0
- package/dist/common-auth-embedded/logger/engine.d.ts +100 -0
- package/dist/common-auth-embedded/logger/engine.js +246 -0
- package/dist/common-auth-embedded/logger/index.d.ts +6 -0
- package/dist/common-auth-embedded/logger/index.js +3 -0
- package/dist/common-auth-embedded/logger/redact.d.ts +11 -0
- package/dist/common-auth-embedded/logger/redact.js +83 -0
- package/dist/common-auth-embedded/pi-slot/index.d.ts +3 -0
- package/dist/common-auth-embedded/pi-slot/index.js +2 -0
- package/dist/common-auth-embedded/pi-slot/lock.js +212 -0
- package/dist/common-auth-embedded/pi-slot/slot.d.ts +49 -0
- package/dist/common-auth-embedded/pi-slot/slot.js +383 -0
- package/dist/common-auth-embedded/quota/codec.d.ts +11 -0
- package/dist/common-auth-embedded/quota/codec.js +12 -0
- package/dist/common-auth-embedded/quota/format.d.ts +46 -0
- package/dist/common-auth-embedded/quota/format.js +138 -0
- package/dist/common-auth-embedded/quota/index.d.ts +10 -0
- package/dist/common-auth-embedded/quota/index.js +5 -0
- package/dist/common-auth-embedded/quota/map.d.ts +63 -0
- package/dist/common-auth-embedded/quota/map.js +105 -0
- package/dist/common-auth-embedded/quota/merge.d.ts +48 -0
- package/dist/common-auth-embedded/quota/merge.js +162 -0
- package/dist/common-auth-embedded/quota/projection.d.ts +64 -0
- package/dist/common-auth-embedded/quota/projection.js +134 -0
- package/dist/common-auth-embedded/routing/admission.d.ts +93 -0
- package/dist/common-auth-embedded/routing/admission.js +142 -0
- package/dist/common-auth-embedded/routing/index.d.ts +8 -0
- package/dist/common-auth-embedded/routing/index.js +4 -0
- package/dist/common-auth-embedded/routing/ordered.d.ts +47 -0
- package/dist/common-auth-embedded/routing/ordered.js +58 -0
- package/dist/common-auth-embedded/routing/pins.d.ts +22 -0
- package/dist/common-auth-embedded/routing/pins.js +34 -0
- package/dist/common-auth-embedded/routing/sticky.d.ts +158 -0
- package/dist/common-auth-embedded/routing/sticky.js +339 -0
- package/dist/common-auth-embedded/source-output.json +1090 -0
- package/dist/common-auth-embedded/store/attribution.d.ts +28 -0
- package/dist/common-auth-embedded/store/attribution.js +60 -0
- package/dist/common-auth-embedded/store/errors.d.ts +55 -0
- package/dist/common-auth-embedded/store/errors.js +38 -0
- package/dist/common-auth-embedded/store/hooks.d.ts +12 -0
- package/dist/common-auth-embedded/store/hooks.js +34 -0
- package/dist/common-auth-embedded/store/identity.d.ts +39 -0
- package/dist/common-auth-embedded/store/identity.js +79 -0
- package/dist/common-auth-embedded/store/index.d.ts +21 -0
- package/dist/common-auth-embedded/store/index.js +7 -0
- package/dist/common-auth-embedded/store/mutate.d.ts +189 -0
- package/dist/common-auth-embedded/store/mutate.js +500 -0
- package/dist/common-auth-embedded/store/pool.d.ts +196 -0
- package/dist/common-auth-embedded/store/pool.js +120 -0
- package/dist/common-auth-embedded/store/provider-state.d.ts +131 -0
- package/dist/common-auth-embedded/store/provider-state.js +226 -0
- package/dist/common-auth-embedded/store/publication.d.ts +47 -0
- package/dist/common-auth-embedded/store/publication.js +304 -0
- package/dist/common-auth-embedded/store/pull.d.ts +36 -0
- package/dist/common-auth-embedded/store/pull.js +143 -0
- package/dist/common-auth-embedded/store/refresh-lock.d.ts +63 -0
- package/dist/common-auth-embedded/store/refresh-lock.js +136 -0
- package/dist/common-auth-embedded/store/refresh.d.ts +72 -0
- package/dist/common-auth-embedded/store/refresh.js +225 -0
- package/dist/common-auth-embedded/store/reserved.js +13 -0
- package/dist/common-auth-embedded/store/rows.d.ts +313 -0
- package/dist/common-auth-embedded/store/rows.js +1258 -0
- package/dist/common-auth-embedded/store/runtime.d.ts +36 -0
- package/dist/common-auth-embedded/store/runtime.js +68 -0
- package/dist/common-auth-embedded/store/schema.d.ts +443 -0
- package/dist/common-auth-embedded/store/schema.js +754 -0
- package/dist/common-auth-embedded/store/settings.d.ts +63 -0
- package/dist/common-auth-embedded/store/settings.js +120 -0
- package/dist/common-auth-embedded/store/torn.d.ts +78 -0
- package/dist/common-auth-embedded/store/torn.js +294 -0
- package/dist/common-auth-embedded/util/any-signal.js +42 -0
- package/dist/common-auth-runtime.d.ts +73 -0
- package/dist/common-auth-runtime.d.ts.map +1 -0
- package/dist/common-auth-runtime.js +65 -0
- package/dist/common-auth-runtime.js.map +1 -0
- package/dist/credential-redaction.d.ts +13 -0
- package/dist/credential-redaction.d.ts.map +1 -0
- package/dist/credential-redaction.js +230 -0
- package/dist/credential-redaction.js.map +1 -0
- package/dist/fetch-timeout.d.ts +4 -2
- package/dist/fetch-timeout.d.ts.map +1 -1
- package/dist/fetch-timeout.js +121 -14
- package/dist/fetch-timeout.js.map +1 -1
- package/dist/fingerprint.d.ts +3 -3
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +4 -4
- package/dist/fingerprint.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/model-registry.d.ts +16 -0
- package/dist/model-registry.d.ts.map +1 -1
- package/dist/model-registry.js +242 -30
- package/dist/model-registry.js.map +1 -1
- package/dist/quota-manager.d.ts.map +1 -1
- package/dist/quota-manager.js +35 -10
- package/dist/quota-manager.js.map +1 -1
- package/dist/quota-presentation.d.ts +10 -0
- package/dist/quota-presentation.d.ts.map +1 -0
- package/dist/quota-presentation.js +60 -0
- package/dist/quota-presentation.js.map +1 -0
- package/dist/quota-types.d.ts +2 -0
- package/dist/quota-types.d.ts.map +1 -1
- package/dist/rotation.d.ts +8 -0
- package/dist/rotation.d.ts.map +1 -1
- package/dist/rotation.js +24 -0
- package/dist/rotation.js.map +1 -1
- package/dist/transform/agy-generation.d.ts +3 -0
- package/dist/transform/agy-generation.d.ts.map +1 -0
- package/dist/transform/agy-generation.js +28 -0
- package/dist/transform/agy-generation.js.map +1 -0
- package/dist/transform/claude.d.ts +7 -3
- package/dist/transform/claude.d.ts.map +1 -1
- package/dist/transform/claude.js +8 -2
- package/dist/transform/claude.js.map +1 -1
- package/dist/transform/index.d.ts +1 -0
- package/dist/transform/index.d.ts.map +1 -1
- package/dist/transform/index.js +1 -0
- package/dist/transform/index.js.map +1 -1
- package/dist/transform/model-resolver.d.ts +2 -1
- package/dist/transform/model-resolver.d.ts.map +1 -1
- package/dist/transform/model-resolver.js +46 -5
- package/dist/transform/model-resolver.js.map +1 -1
- package/dist/transform/types.d.ts +1 -1
- package/dist/transform/types.d.ts.map +1 -1
- package/dist/vault-account-source.d.ts +183 -0
- package/dist/vault-account-source.d.ts.map +1 -0
- package/dist/vault-account-source.js +296 -0
- package/dist/vault-account-source.js.map +1 -0
- package/dist/vault-enrollment-authority.d.ts +16 -0
- package/dist/vault-enrollment-authority.d.ts.map +1 -0
- package/dist/vault-enrollment-authority.js +19 -0
- package/dist/vault-enrollment-authority.js.map +1 -0
- package/dist/vault-family.d.ts +11 -0
- package/dist/vault-family.d.ts.map +1 -0
- package/dist/vault-family.js +24 -0
- package/dist/vault-family.js.map +1 -0
- package/dist/vault-provider-state.d.ts +162 -0
- package/dist/vault-provider-state.d.ts.map +1 -0
- package/dist/vault-provider-state.js +422 -0
- package/dist/vault-provider-state.js.map +1 -0
- package/package.json +39 -2
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { type Attribution } from './attribution.js';
|
|
2
|
+
import type { PoolOperationError } from './errors.js';
|
|
3
|
+
import type { PoolLogger } from './hooks.js';
|
|
4
|
+
import { type HoldPoint, type InitializeOutcome, type StoreContext } from './mutate.js';
|
|
5
|
+
import { type ProviderStateMutator, type UpdateProviderStateResult } from './provider-state.js';
|
|
6
|
+
import { type PublicationReceipt, type PublishOptions, type PublishPlan, type PublishResult } from './publication.js';
|
|
7
|
+
import { type PullHook } from './pull.js';
|
|
8
|
+
import { type ProviderRefresh, type RefreshOptions, type RefreshOutcome } from './refresh.js';
|
|
9
|
+
import { type LockEnvironment, type PoolLockOptions, type PoolLockSpec } from './refresh-lock.js';
|
|
10
|
+
import { type AddInput, type AddOptions, type AddResult, type CredentialWriteInput, type RemoveOptions, type RemoveResult, type ReorderOptions, type ReorderResult, type RowOperationOptions, type RowToggleOptions, type RowTransitionOptions, type RowTransitionResult, type RowWriteOptions } from './rows.js';
|
|
11
|
+
import { type PoolCredential, type PoolRow, type ProviderStateCodec, type QuotaCodec, type RotateCredential, type StoredCredential } from './schema.js';
|
|
12
|
+
import { type SettingsMutator, type SettingsRead, type UpdateSettingsOptions, type UpdateSettingsResult } from './settings.js';
|
|
13
|
+
export interface OpenPoolStoreOptions {
|
|
14
|
+
/** The provider every row of this pool belongs to; keys the provider-wide lock. */
|
|
15
|
+
provider: string;
|
|
16
|
+
configPath: string;
|
|
17
|
+
statePath: string;
|
|
18
|
+
quota: QuotaCodec;
|
|
19
|
+
/**
|
|
20
|
+
* The codec of the provider state kept beside each row's credential (see
|
|
21
|
+
* `ProviderStateCodec`). Without it no row shows a provider state, and
|
|
22
|
+
* every write that would set one refuses (`invalid-input`); writes that
|
|
23
|
+
* leave it alone keep the value on disk as it is, and `replace` clears it.
|
|
24
|
+
*/
|
|
25
|
+
providerState?: ProviderStateCodec;
|
|
26
|
+
/**
|
|
27
|
+
* Refuse every credential this store did not stamp (default false, which
|
|
28
|
+
* loads unstamped and mis-stamped credentials as older writers left them).
|
|
29
|
+
* When true, a row whose `stamp` is not `bound` loads `unbound` and is no
|
|
30
|
+
* candidate, and every operation that would use or keep its credential or
|
|
31
|
+
* what was observed about it (`refresh`, quota pulls, `recordQuota`,
|
|
32
|
+
* `recordIdentity`, `rotate`, and an `add` of the same secret or onto the
|
|
33
|
+
* same credential-less row) refuses with `unbound-credential` before any
|
|
34
|
+
* provider call or write, checked again under the locks and at commit.
|
|
35
|
+
* Nothing makes such a row bound except `replace`, which starts a new
|
|
36
|
+
* credential epoch and drops what was observed about the old one.
|
|
37
|
+
* An interrupted add's state-only orphan is recovered only when its stamp
|
|
38
|
+
* binds the stored and incoming material, including a supplied identity and
|
|
39
|
+
* an API key's endpoint/header. Otherwise add refuses `unbound-credential`
|
|
40
|
+
* without writing; `remove(id)` explicitly discards the orphan.
|
|
41
|
+
*/
|
|
42
|
+
requireCredentialStamps?: boolean;
|
|
43
|
+
/** Require an exact secret fingerprint for every roster-publication removal. */
|
|
44
|
+
requireRemovedFingerprint?: boolean;
|
|
45
|
+
/** Injected clock for leases, refresh stamps and `addedAt`. */
|
|
46
|
+
now?: () => number;
|
|
47
|
+
/**
|
|
48
|
+
* Ordered store-lock list held across every read-modify-write. Defaults to
|
|
49
|
+
* the older writers' `save` lock at the config path, then at the state path.
|
|
50
|
+
*/
|
|
51
|
+
storeLocks?: readonly PoolLockSpec[];
|
|
52
|
+
/** Defaults for every lock the store takes (see `POOL_LOCK_DEFAULTS`). */
|
|
53
|
+
lockOptions?: Partial<PoolLockOptions>;
|
|
54
|
+
/** Overrides for the row locks only. */
|
|
55
|
+
rowLockOptions?: Partial<PoolLockOptions>;
|
|
56
|
+
/** The provider-wide lock; defaults to `provider-<provider>` beside the state file. */
|
|
57
|
+
providerLock?: PoolLockSpec;
|
|
58
|
+
/** Quota pull hook, fired without being awaited. */
|
|
59
|
+
pull?: PullHook;
|
|
60
|
+
/** Receives every pull failure, since no caller awaits a pull. */
|
|
61
|
+
onPullFailure?: (rowId: string, error: PoolOperationError) => void | Promise<void>;
|
|
62
|
+
logger?: PoolLogger;
|
|
63
|
+
/** Named write steps, awaited; a test seam for crash and ownership rows. */
|
|
64
|
+
onStep?: StoreContext['onStep'];
|
|
65
|
+
/** Awaitable hold points on the refresh and pull paths; a test seam. */
|
|
66
|
+
hold?: (point: HoldPoint, rowId: string) => void | Promise<void>;
|
|
67
|
+
onLockEvent?: LockEnvironment['onLockEvent'];
|
|
68
|
+
onLockStep?: LockEnvironment['onLockStep'];
|
|
69
|
+
}
|
|
70
|
+
export type PoolLoad = {
|
|
71
|
+
status: 'ready';
|
|
72
|
+
schemaVersion: number;
|
|
73
|
+
rows: PoolRow[];
|
|
74
|
+
} | {
|
|
75
|
+
status: 'pending-migration';
|
|
76
|
+
roster: unknown[];
|
|
77
|
+
} | {
|
|
78
|
+
status: 'error';
|
|
79
|
+
file: 'config' | 'state';
|
|
80
|
+
reason: string;
|
|
81
|
+
kind?: 'snapshot-contended';
|
|
82
|
+
retryable?: true;
|
|
83
|
+
};
|
|
84
|
+
export interface PoolStore {
|
|
85
|
+
/** Reads the pool and fires first-reading pulls; never writes a file itself. */
|
|
86
|
+
load(): Promise<PoolLoad>;
|
|
87
|
+
/** Reads the pool without firing anything. */
|
|
88
|
+
read(): Promise<PoolLoad>;
|
|
89
|
+
/**
|
|
90
|
+
* Turns a pending-migration config into an empty pool (the pool key with no
|
|
91
|
+
* row entries) in one locked config write, dropping the named top-level
|
|
92
|
+
* keys and keeping the rest. The start of a plugin's migration; a ready
|
|
93
|
+
* pool is left alone. Failures carry operation `initialize`.
|
|
94
|
+
*/
|
|
95
|
+
initialize(input?: {
|
|
96
|
+
dropKeys?: readonly string[];
|
|
97
|
+
}): Promise<{
|
|
98
|
+
status: InitializeOutcome;
|
|
99
|
+
}>;
|
|
100
|
+
/**
|
|
101
|
+
* Adds a row, or completes or rotates the row already holding the id or
|
|
102
|
+
* the secret. A new row starts at credential epoch 1; since 0.8.0 one
|
|
103
|
+
* whose id the pool held before starts one past the highest epoch that id
|
|
104
|
+
* held (see `Attribution`), and an id that held `Number.MAX_SAFE_INTEGER`
|
|
105
|
+
* refuses (`id-removed`) before writing.
|
|
106
|
+
*/
|
|
107
|
+
add(input: AddInput, options?: AddOptions): Promise<AddResult>;
|
|
108
|
+
publishRoster(plan: PublishPlan, options?: PublishOptions): Promise<PublishResult>;
|
|
109
|
+
publication(operationId: string): Promise<PublicationReceipt | undefined>;
|
|
110
|
+
/**
|
|
111
|
+
* Gives a row a new credential and a new credential epoch. Since 0.6.0 the
|
|
112
|
+
* row's provider state is whatever `ProviderStateCodec.onReplace` returns;
|
|
113
|
+
* without that hook it is `input.providerState`, else cleared.
|
|
114
|
+
* Optional `attribution` fences the write on the prior credential under the
|
|
115
|
+
* locks, before any write or replacement hook; see `RowWriteOptions`.
|
|
116
|
+
*/
|
|
117
|
+
replace(id: string, credential: PoolCredential, input?: CredentialWriteInput, options?: RowWriteOptions): Promise<{
|
|
118
|
+
id: string;
|
|
119
|
+
credential: StoredCredential;
|
|
120
|
+
credentialEpoch: number;
|
|
121
|
+
}>;
|
|
122
|
+
/**
|
|
123
|
+
* Refreshes the secret a row holds without changing its account or
|
|
124
|
+
* endpoint. Since 0.4.1 an API key may leave out `baseURL` and `authHeader`
|
|
125
|
+
* to keep the row's, and one that gives another is refused
|
|
126
|
+
* (`endpoint-mismatch`) before writing: that is a `replace`.
|
|
127
|
+
* Takes the same optional attribution fence as `replace` (`RowWriteOptions`).
|
|
128
|
+
*/
|
|
129
|
+
rotate(id: string, credential: RotateCredential, input?: CredentialWriteInput, options?: RowWriteOptions): Promise<{
|
|
130
|
+
id: string;
|
|
131
|
+
credential: StoredCredential;
|
|
132
|
+
}>;
|
|
133
|
+
/**
|
|
134
|
+
* Changes a row's provider state without touching its credential (since
|
|
135
|
+
* 0.6.0), under the row lock, `extraLocks` and the store locks. Refuses
|
|
136
|
+
* (`attribution`) once the row has moved off the credential epoch or
|
|
137
|
+
* identity in `fence`, and (`unknown-row`) once it is removed.
|
|
138
|
+
*/
|
|
139
|
+
updateProviderState(id: string, fence: Attribution, mutator: ProviderStateMutator, options?: RowToggleOptions): Promise<UpdateProviderStateResult>;
|
|
140
|
+
/**
|
|
141
|
+
* Sets `enabled: false` and the entry's `disabledReason`. Takes the row
|
|
142
|
+
* lock, then `extraLocks`, then the store locks (the row lock and
|
|
143
|
+
* `extraLocks` since 0.2.3). Since 0.7.0 it may be fenced on the
|
|
144
|
+
* credential the caller's evidence is about (`attribution`) and carry a
|
|
145
|
+
* provider-state change that lands with it (`providerState`); see
|
|
146
|
+
* `RowTransitionOptions`.
|
|
147
|
+
*/
|
|
148
|
+
disable(id: string, reason: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
|
|
149
|
+
/**
|
|
150
|
+
* Clears `enabled: false` and `disabledReason` (since 0.2.3); refuses with
|
|
151
|
+
* `duplicate-identity` when another enabled OAuth row holds the row's
|
|
152
|
+
* identity. Locks as `disable`, and takes the same options since 0.7.0.
|
|
153
|
+
*/
|
|
154
|
+
enable(id: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
|
|
155
|
+
/**
|
|
156
|
+
* Deletes the roster row, its per-row entry and its state-file credential
|
|
157
|
+
* (since 0.2.3). Locks as `disable`; `protect` can refuse the id.
|
|
158
|
+
*/
|
|
159
|
+
remove(id: string, options?: RemoveOptions): Promise<RemoveResult>;
|
|
160
|
+
/**
|
|
161
|
+
* Sets the roster order (since 0.2.4) in one config write. `ids` must name
|
|
162
|
+
* every roster id exactly once; anything else refuses with `invalid-order`
|
|
163
|
+
* and writes nothing. Takes `extraLocks`, then the store locks; no row or
|
|
164
|
+
* provider-wide lock. Roster rows and their entries are left unchanged.
|
|
165
|
+
*/
|
|
166
|
+
reorder(ids: readonly string[], options?: ReorderOptions): Promise<ReorderResult>;
|
|
167
|
+
/**
|
|
168
|
+
* The plugin's settings (since 0.2.6): every top-level key of the config
|
|
169
|
+
* file except the pool-owned ones (`POOL_OWNED_KEYS`). Never writes.
|
|
170
|
+
*/
|
|
171
|
+
readSettings(): Promise<SettingsRead>;
|
|
172
|
+
/**
|
|
173
|
+
* One locked write of the plugin's settings (since 0.2.6) beside the pool,
|
|
174
|
+
* in the config file. Refuses a result that sets a pool-owned key
|
|
175
|
+
* (`invalid-input`). Takes `extraLocks`, then the store locks.
|
|
176
|
+
*/
|
|
177
|
+
updateSettings(mutator: SettingsMutator, options?: UpdateSettingsOptions): Promise<UpdateSettingsResult>;
|
|
178
|
+
/**
|
|
179
|
+
* Records the identity a lookup found for the row's credential. Since
|
|
180
|
+
* 0.3.1 it takes the credential epoch the lookup was issued for, and
|
|
181
|
+
* refuses a lookup that completes after the row was replaced
|
|
182
|
+
* (`attribution`) or a row recorded for another account
|
|
183
|
+
* (`identity-mismatch`).
|
|
184
|
+
*/
|
|
185
|
+
recordIdentity(id: string, identity: string, attribution: Pick<Attribution, 'credentialEpoch'>, options?: RowOperationOptions): Promise<{
|
|
186
|
+
id: string;
|
|
187
|
+
disabled: string[];
|
|
188
|
+
}>;
|
|
189
|
+
refresh(id: string, provider: ProviderRefresh, options?: RefreshOptions): Promise<RefreshOutcome>;
|
|
190
|
+
recordQuota(id: string, attribution: Attribution, observation: unknown): Promise<void>;
|
|
191
|
+
/** Fires a pull for a row admission refused for want of a reading. */
|
|
192
|
+
requestReading(id: string): void;
|
|
193
|
+
/** Resolves once every pull fired so far has settled. */
|
|
194
|
+
pullsSettled(): Promise<void>;
|
|
195
|
+
}
|
|
196
|
+
export declare function openPoolStore(options: OpenPoolStoreOptions): PoolStore;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { recordQuota } from './attribution.js';
|
|
2
|
+
import { initializePool, readPool, } from './mutate.js';
|
|
3
|
+
import { updateProviderStateRow, } from './provider-state.js';
|
|
4
|
+
import { publication, publishRoster, } from './publication.js';
|
|
5
|
+
import { PullScheduler } from './pull.js';
|
|
6
|
+
import { refreshRow, } from './refresh.js';
|
|
7
|
+
import { POOL_LOCK_DEFAULTS, } from './refresh-lock.js';
|
|
8
|
+
import { addRow, disableRow, enableRow, recordRowIdentity, removeRow, reorderRows, replaceRow, rotateRow, } from './rows.js';
|
|
9
|
+
import { POOL_SCHEMA_VERSION, } from './schema.js';
|
|
10
|
+
import { readPoolSettings, updatePoolSettings, } from './settings.js';
|
|
11
|
+
/**
|
|
12
|
+
* Process-wide memory per config file: ids whose per-row entry a library
|
|
13
|
+
* write dropped (not reused in this process), and rows a load-time pull
|
|
14
|
+
* already fired for.
|
|
15
|
+
*/
|
|
16
|
+
const processMemory = new Map();
|
|
17
|
+
function memoryFor(configPath) {
|
|
18
|
+
let memory = processMemory.get(configPath);
|
|
19
|
+
if (!memory) {
|
|
20
|
+
memory = { removedIds: new Set(), firedAtLoad: new Set() };
|
|
21
|
+
processMemory.set(configPath, memory);
|
|
22
|
+
}
|
|
23
|
+
return memory;
|
|
24
|
+
}
|
|
25
|
+
function toLoad(result) {
|
|
26
|
+
if (result.status === 'ready')
|
|
27
|
+
return {
|
|
28
|
+
status: 'ready',
|
|
29
|
+
schemaVersion: POOL_SCHEMA_VERSION,
|
|
30
|
+
rows: result.rows,
|
|
31
|
+
};
|
|
32
|
+
if (result.status === 'pending-migration')
|
|
33
|
+
return {
|
|
34
|
+
status: 'pending-migration',
|
|
35
|
+
roster: Array.isArray(result.config.accounts)
|
|
36
|
+
? result.config.accounts
|
|
37
|
+
: [],
|
|
38
|
+
};
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
export function openPoolStore(options) {
|
|
42
|
+
const memory = memoryFor(options.configPath);
|
|
43
|
+
const lockDefaults = {
|
|
44
|
+
...POOL_LOCK_DEFAULTS,
|
|
45
|
+
...options.lockOptions,
|
|
46
|
+
};
|
|
47
|
+
const ctx = {
|
|
48
|
+
provider: options.provider,
|
|
49
|
+
configPath: options.configPath,
|
|
50
|
+
statePath: options.statePath,
|
|
51
|
+
codec: options.quota,
|
|
52
|
+
...(options.providerState ? { providerState: options.providerState } : {}),
|
|
53
|
+
now: options.now ?? Date.now,
|
|
54
|
+
storeLocks: options.storeLocks ?? [
|
|
55
|
+
{ name: 'save', path: options.configPath },
|
|
56
|
+
{ name: 'save', path: options.statePath },
|
|
57
|
+
],
|
|
58
|
+
lockDefaults,
|
|
59
|
+
lockEnv: {
|
|
60
|
+
now: options.now ?? Date.now,
|
|
61
|
+
...(options.onLockEvent ? { onLockEvent: options.onLockEvent } : {}),
|
|
62
|
+
...(options.onLockStep ? { onLockStep: options.onLockStep } : {}),
|
|
63
|
+
},
|
|
64
|
+
removedIds: memory.removedIds,
|
|
65
|
+
requireCredentialStamps: options.requireCredentialStamps === true,
|
|
66
|
+
requireRemovedFingerprint: options.requireRemovedFingerprint === true,
|
|
67
|
+
...(options.logger ? { logger: options.logger } : {}),
|
|
68
|
+
...(options.onStep ? { onStep: options.onStep } : {}),
|
|
69
|
+
...(options.hold ? { hold: options.hold } : {}),
|
|
70
|
+
};
|
|
71
|
+
const pulls = new PullScheduler(() => rt, options.pull, options.onPullFailure, memory.firedAtLoad, options.logger);
|
|
72
|
+
const rt = {
|
|
73
|
+
ctx,
|
|
74
|
+
providerLock: options.providerLock ?? {
|
|
75
|
+
name: `provider-${encodeURIComponent(options.provider)}`,
|
|
76
|
+
path: options.statePath,
|
|
77
|
+
},
|
|
78
|
+
rowLockOptions: options.rowLockOptions ?? {},
|
|
79
|
+
firePull: (id, reason) => pulls.fire(id, reason),
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
async load() {
|
|
83
|
+
const result = await readPool(ctx);
|
|
84
|
+
if (result.status === 'ready') {
|
|
85
|
+
// A torn row is no candidate, but its pull is fired too: the pull
|
|
86
|
+
// completes the row on disk before reading it, so an interrupted
|
|
87
|
+
// replace of an OAuth row heals at the next load.
|
|
88
|
+
for (const row of result.rows)
|
|
89
|
+
if ((row.candidate || (row.torn && row.enabled)) &&
|
|
90
|
+
row.type === 'oauth' &&
|
|
91
|
+
row.needsFirstReading)
|
|
92
|
+
pulls.fire(row.id, 'load');
|
|
93
|
+
}
|
|
94
|
+
return toLoad(result);
|
|
95
|
+
},
|
|
96
|
+
async read() {
|
|
97
|
+
return toLoad(await readPool(ctx));
|
|
98
|
+
},
|
|
99
|
+
async initialize(input = {}) {
|
|
100
|
+
return { status: await initializePool(ctx, input.dropKeys ?? []) };
|
|
101
|
+
},
|
|
102
|
+
add: (input, callOptions) => addRow(rt, input, callOptions),
|
|
103
|
+
publishRoster: (plan, callOptions) => publishRoster(rt, plan, callOptions),
|
|
104
|
+
publication: (operationId) => publication(rt, operationId),
|
|
105
|
+
replace: (id, credential, input, callOptions) => replaceRow(rt, id, credential, input, callOptions),
|
|
106
|
+
rotate: (id, credential, input, callOptions) => rotateRow(rt, id, credential, input, callOptions),
|
|
107
|
+
updateProviderState: (id, fence, mutator, callOptions) => updateProviderStateRow(rt, id, fence, mutator, callOptions),
|
|
108
|
+
disable: (id, reason, callOptions) => disableRow(rt, id, reason, callOptions),
|
|
109
|
+
enable: (id, callOptions) => enableRow(rt, id, callOptions),
|
|
110
|
+
remove: (id, callOptions) => removeRow(rt, id, callOptions),
|
|
111
|
+
reorder: (ids, callOptions) => reorderRows(rt, ids, callOptions),
|
|
112
|
+
readSettings: () => readPoolSettings(rt),
|
|
113
|
+
updateSettings: (mutator, callOptions) => updatePoolSettings(rt, mutator, callOptions),
|
|
114
|
+
recordIdentity: (id, identity, attribution, callOptions) => recordRowIdentity(rt, id, identity, attribution, callOptions),
|
|
115
|
+
refresh: (id, provider, callOptions) => refreshRow(rt, id, provider, callOptions),
|
|
116
|
+
recordQuota: (id, attribution, observation) => recordQuota(rt, id, attribution, observation),
|
|
117
|
+
requestReading: (id) => pulls.fire(id, 'admission'),
|
|
118
|
+
pullsSettled: () => pulls.settled(),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Attribution } from './attribution.js';
|
|
2
|
+
import type { PoolOperation } from './errors.js';
|
|
3
|
+
import { type Transaction } from './mutate.js';
|
|
4
|
+
import type { RowToggleOptions } from './rows.js';
|
|
5
|
+
import { type StoreRuntime } from './runtime.js';
|
|
6
|
+
import { type PoolRow, type ProviderStateCodec } from './schema.js';
|
|
7
|
+
/**
|
|
8
|
+
* What a credential write does to the provider state beside it: `keep`
|
|
9
|
+
* leaves the value on disk as it is (bound by the new stamp only if the old
|
|
10
|
+
* stamp bound it to the same row, epoch and identity), `set` stores a value
|
|
11
|
+
* the codec accepted, and `clear` deletes it.
|
|
12
|
+
*/
|
|
13
|
+
export type ProviderStateWrite = {
|
|
14
|
+
kind: 'keep';
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'set';
|
|
17
|
+
value: unknown;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'clear';
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A provider state as the store will store it: a JSON round trip of it (so
|
|
23
|
+
* its digest is the same once read back from disk), accepted by the codec.
|
|
24
|
+
* Refused before anything is written when the store has no codec, the value
|
|
25
|
+
* is not JSON, or the codec rejects it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function acceptProviderState(codec: ProviderStateCodec | undefined, operation: PoolOperation, id: string, value: unknown, what?: string): unknown;
|
|
28
|
+
/**
|
|
29
|
+
* The write for a value a credential write brings (`add` of a secret the
|
|
30
|
+
* pool holds, `rotate`, a refresh): merged with the row's value on disk by
|
|
31
|
+
* the codec's `merge` when both exist, else the incoming value as is.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mergedProviderState(codec: ProviderStateCodec | undefined, operation: PoolOperation, id: string, onDisk: unknown, incoming: unknown): ProviderStateWrite;
|
|
34
|
+
/**
|
|
35
|
+
* The provider state a replace leaves on the row, decided before anything is
|
|
36
|
+
* written: whatever the codec's `onReplace` returns (undefined clears it), or
|
|
37
|
+
* without that hook the value handed to `replace`, else nothing. The old
|
|
38
|
+
* credential's value is never kept by default: it describes the account the
|
|
39
|
+
* replaced credential belonged to.
|
|
40
|
+
*/
|
|
41
|
+
export declare function replacementProviderState(codec: ProviderStateCodec | undefined, row: PoolRow, credentialEpoch: number, identity: string | undefined, incoming: unknown): ProviderStateWrite;
|
|
42
|
+
/**
|
|
43
|
+
* The provider-state digest the stamp of a credential write carries, and the
|
|
44
|
+
* state-file fields it changes. `set` binds the new value; `keep` carries the
|
|
45
|
+
* old stamp's digest forward only when that stamp bound it to this row as it
|
|
46
|
+
* stood before the write (same credential lineage, the epoch being written,
|
|
47
|
+
* the identity recorded before the write); `clear` binds nothing.
|
|
48
|
+
*/
|
|
49
|
+
export declare function providerStateCoverage(codec: ProviderStateCodec | undefined, write: ProviderStateWrite, prior: Record<string, unknown> | undefined, priorRow: PoolRow | undefined, credentialEpoch: number): string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Receives a private copy of the row's provider state (undefined when the row
|
|
52
|
+
* shows none) and the row as loaded under the locks, and returns the next
|
|
53
|
+
* provider state; returning undefined clears it, so a mutator that means to
|
|
54
|
+
* keep the value returns it. It runs under the row lock and the store locks,
|
|
55
|
+
* so it must not call back into the store (`PoolReentryError`).
|
|
56
|
+
*/
|
|
57
|
+
export type ProviderStateMutator = (current: unknown | undefined, row: PoolRow) => unknown | Promise<unknown>;
|
|
58
|
+
export type UpdateProviderStateResult = {
|
|
59
|
+
id: string;
|
|
60
|
+
/** The provider state now on disk; absent when the row has none. */
|
|
61
|
+
providerState?: unknown;
|
|
62
|
+
/**
|
|
63
|
+
* `unchanged`: the mutator returned what the row already shows (or cleared
|
|
64
|
+
* a row that holds none); nothing was written.
|
|
65
|
+
*/
|
|
66
|
+
outcome: 'updated' | 'cleared' | 'unchanged';
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Changes a row's provider state without touching its credential, in one
|
|
70
|
+
* state-file write under the row lock, the caller's extra locks and the
|
|
71
|
+
* store locks. `fence` is what the caller read the row at: the write is
|
|
72
|
+
* refused (`attribution`, retryable) when the row has since moved to another
|
|
73
|
+
* credential epoch or recorded identity, and (`unknown-row`) once it is
|
|
74
|
+
* removed, so a writer that read the row before a replace or a removal never
|
|
75
|
+
* lands its value on the new credential or brings a removed row's state back.
|
|
76
|
+
*
|
|
77
|
+
* The value is bound by the stamp already beside the credential. When its
|
|
78
|
+
* credential-bound part is unchanged the stamp is left byte for byte as it
|
|
79
|
+
* is; otherwise only the stamp's provider-state digest changes, so the
|
|
80
|
+
* credential's stamp status never moves. With `requireCredentialStamps`, an
|
|
81
|
+
* unbound row refuses (`unbound-credential`) as every other strict path
|
|
82
|
+
* does. Without it, a row whose stamp was not written by this store with this
|
|
83
|
+
* credential at the row's epoch and identity refuses the same way: no stamp
|
|
84
|
+
* could bind the value, so no reader would ever show it. A `rotate` or
|
|
85
|
+
* `replace` stamps such a row.
|
|
86
|
+
*/
|
|
87
|
+
export declare function updateProviderStateRow(rt: StoreRuntime, id: string, fence: Attribution, mutator: ProviderStateMutator, options?: RowToggleOptions): Promise<UpdateProviderStateResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Returned by the provider-state mutator of an attributed `disable` or
|
|
90
|
+
* `enable` to decline the whole transition: nothing is written, neither the
|
|
91
|
+
* provider state nor the row's enabled flag, and the call resolves with
|
|
92
|
+
* `declined: true`. A mutator declines when the state it is shown is newer
|
|
93
|
+
* than what its caller saw, such as an eligibility recorded after the
|
|
94
|
+
* request whose refusal is being acted on. It is a value of its own because
|
|
95
|
+
* `undefined` already means "clear the provider state". `Symbol.for` keeps it
|
|
96
|
+
* equal across two copies of this module loaded in one process.
|
|
97
|
+
*/
|
|
98
|
+
export declare const DECLINE_TRANSITION: unique symbol;
|
|
99
|
+
/**
|
|
100
|
+
* The provider-state mutator of an attributed `disable` or `enable`: as
|
|
101
|
+
* `ProviderStateMutator`, and it may also return `DECLINE_TRANSITION`.
|
|
102
|
+
*/
|
|
103
|
+
export type RowTransitionMutator = (current: unknown | undefined, row: PoolRow) => unknown | typeof DECLINE_TRANSITION | Promise<unknown | typeof DECLINE_TRANSITION>;
|
|
104
|
+
/**
|
|
105
|
+
* What a provider-state mutator asks of a row, worked out under the locks
|
|
106
|
+
* before anything is written. `changed` carries the row's whole next
|
|
107
|
+
* state-file account entry (the value, and the stamp rebound to it when its
|
|
108
|
+
* credential-bound part moved); `value` is the next value, absent when it is
|
|
109
|
+
* cleared.
|
|
110
|
+
*/
|
|
111
|
+
export type ProviderStatePlan = {
|
|
112
|
+
kind: 'declined';
|
|
113
|
+
} | {
|
|
114
|
+
kind: 'unchanged';
|
|
115
|
+
value?: unknown;
|
|
116
|
+
} | {
|
|
117
|
+
kind: 'changed';
|
|
118
|
+
value?: unknown;
|
|
119
|
+
account: Record<string, unknown>;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Runs a provider-state mutator for a row loaded under every lock and
|
|
123
|
+
* already checked by the caller (present, valid, inside its attribution
|
|
124
|
+
* fence), and plans the write. Refuses (`no-credential`) a row holding no
|
|
125
|
+
* credential, and (`unbound-credential`) one whose credential carries no
|
|
126
|
+
* stamp of this store at the row's epoch and identity: no stamp could bind
|
|
127
|
+
* the value, so no reader would ever show it. `DECLINE_TRANSITION` is
|
|
128
|
+
* honoured only when `declinable` is set; elsewhere it is not JSON and is
|
|
129
|
+
* refused as such.
|
|
130
|
+
*/
|
|
131
|
+
export declare function planProviderStateIn(tx: Transaction, codec: ProviderStateCodec, operation: PoolOperation, row: PoolRow, mutator: ProviderStateMutator | RowTransitionMutator, declinable?: boolean): Promise<ProviderStatePlan>;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { assertNotInsideHook, runInsideHook } from './hooks.js';
|
|
2
|
+
import { runOperation, withTransaction } from './mutate.js';
|
|
3
|
+
import { readRow, refusal, requireBound, rowLockSpec, unknownRow, } from './runtime.js';
|
|
4
|
+
import { boundProviderStateDigest, CREDENTIAL_STAMP_KEY, credentialDigest, isCredentialEpoch, isRecord, PROVIDER_STATE_KEY, parseStamp, providerStateDigest, rowLockKey, } from './schema.js';
|
|
5
|
+
/**
|
|
6
|
+
* A provider state as the store will store it: a JSON round trip of it (so
|
|
7
|
+
* its digest is the same once read back from disk), accepted by the codec.
|
|
8
|
+
* Refused before anything is written when the store has no codec, the value
|
|
9
|
+
* is not JSON, or the codec rejects it.
|
|
10
|
+
*/
|
|
11
|
+
export function acceptProviderState(codec, operation, id, value, what = 'the provider state') {
|
|
12
|
+
if (!codec)
|
|
13
|
+
throw refusal(operation, id, 'invalid-input', 'the store was opened without a provider-state codec');
|
|
14
|
+
let normalized;
|
|
15
|
+
try {
|
|
16
|
+
const text = JSON.stringify(value);
|
|
17
|
+
normalized = text === undefined ? undefined : JSON.parse(text);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
normalized = undefined;
|
|
21
|
+
}
|
|
22
|
+
if (normalized === undefined)
|
|
23
|
+
throw refusal(operation, id, 'invalid-provider-state', `${what} cannot be stored as JSON`);
|
|
24
|
+
if (!codec.validate(normalized))
|
|
25
|
+
throw refusal(operation, id, 'invalid-provider-state', `the provider-state codec rejected ${what}`);
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The write for a value a credential write brings (`add` of a secret the
|
|
30
|
+
* pool holds, `rotate`, a refresh): merged with the row's value on disk by
|
|
31
|
+
* the codec's `merge` when both exist, else the incoming value as is.
|
|
32
|
+
*/
|
|
33
|
+
export function mergedProviderState(codec, operation, id, onDisk, incoming) {
|
|
34
|
+
if (onDisk === undefined || !codec?.merge)
|
|
35
|
+
return { kind: 'set', value: incoming };
|
|
36
|
+
return {
|
|
37
|
+
kind: 'set',
|
|
38
|
+
value: acceptProviderState(codec, operation, id, codec.merge(structuredClone(onDisk), structuredClone(incoming)), 'the merged provider state'),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The provider state a replace leaves on the row, decided before anything is
|
|
43
|
+
* written: whatever the codec's `onReplace` returns (undefined clears it), or
|
|
44
|
+
* without that hook the value handed to `replace`, else nothing. The old
|
|
45
|
+
* credential's value is never kept by default: it describes the account the
|
|
46
|
+
* replaced credential belonged to.
|
|
47
|
+
*/
|
|
48
|
+
export function replacementProviderState(codec, row, credentialEpoch, identity, incoming) {
|
|
49
|
+
const hook = codec?.onReplace;
|
|
50
|
+
const next = hook
|
|
51
|
+
? hook(row.providerState === undefined
|
|
52
|
+
? undefined
|
|
53
|
+
: structuredClone(row.providerState), {
|
|
54
|
+
id: row.id,
|
|
55
|
+
credentialEpoch,
|
|
56
|
+
...(row.identity !== undefined
|
|
57
|
+
? { previousIdentity: row.identity }
|
|
58
|
+
: {}),
|
|
59
|
+
...(identity !== undefined ? { identity } : {}),
|
|
60
|
+
...(incoming !== undefined
|
|
61
|
+
? { incoming: structuredClone(incoming) }
|
|
62
|
+
: {}),
|
|
63
|
+
})
|
|
64
|
+
: incoming;
|
|
65
|
+
if (next === undefined)
|
|
66
|
+
return { kind: 'clear' };
|
|
67
|
+
if (!hook)
|
|
68
|
+
return { kind: 'set', value: next };
|
|
69
|
+
return {
|
|
70
|
+
kind: 'set',
|
|
71
|
+
value: acceptProviderState(codec, 'replace', row.id, next, 'the provider state onReplace returned'),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The provider-state digest the stamp of a credential write carries, and the
|
|
76
|
+
* state-file fields it changes. `set` binds the new value; `keep` carries the
|
|
77
|
+
* old stamp's digest forward only when that stamp bound it to this row as it
|
|
78
|
+
* stood before the write (same credential lineage, the epoch being written,
|
|
79
|
+
* the identity recorded before the write); `clear` binds nothing.
|
|
80
|
+
*/
|
|
81
|
+
export function providerStateCoverage(codec, write, prior, priorRow, credentialEpoch) {
|
|
82
|
+
if (write.kind === 'set')
|
|
83
|
+
return providerStateDigest(codec, write.value);
|
|
84
|
+
if (write.kind === 'clear')
|
|
85
|
+
return undefined;
|
|
86
|
+
return boundProviderStateDigest(prior, priorRow?.credential, credentialEpoch, priorRow?.identity);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Changes a row's provider state without touching its credential, in one
|
|
90
|
+
* state-file write under the row lock, the caller's extra locks and the
|
|
91
|
+
* store locks. `fence` is what the caller read the row at: the write is
|
|
92
|
+
* refused (`attribution`, retryable) when the row has since moved to another
|
|
93
|
+
* credential epoch or recorded identity, and (`unknown-row`) once it is
|
|
94
|
+
* removed, so a writer that read the row before a replace or a removal never
|
|
95
|
+
* lands its value on the new credential or brings a removed row's state back.
|
|
96
|
+
*
|
|
97
|
+
* The value is bound by the stamp already beside the credential. When its
|
|
98
|
+
* credential-bound part is unchanged the stamp is left byte for byte as it
|
|
99
|
+
* is; otherwise only the stamp's provider-state digest changes, so the
|
|
100
|
+
* credential's stamp status never moves. With `requireCredentialStamps`, an
|
|
101
|
+
* unbound row refuses (`unbound-credential`) as every other strict path
|
|
102
|
+
* does. Without it, a row whose stamp was not written by this store with this
|
|
103
|
+
* credential at the row's epoch and identity refuses the same way: no stamp
|
|
104
|
+
* could bind the value, so no reader would ever show it. A `rotate` or
|
|
105
|
+
* `replace` stamps such a row.
|
|
106
|
+
*/
|
|
107
|
+
export async function updateProviderStateRow(rt, id, fence, mutator, options = {}) {
|
|
108
|
+
assertNotInsideHook('updateProviderState');
|
|
109
|
+
const { ctx } = rt;
|
|
110
|
+
const operation = 'updateProviderState';
|
|
111
|
+
return runOperation(ctx, operation, id, options.onFailure, async (locks, progress) => {
|
|
112
|
+
const codec = ctx.providerState;
|
|
113
|
+
if (!codec)
|
|
114
|
+
throw refusal(operation, id, 'invalid-input', 'the store was opened without a provider-state codec');
|
|
115
|
+
if (typeof mutator !== 'function')
|
|
116
|
+
throw refusal(operation, id, 'invalid-input', 'a mutator is required');
|
|
117
|
+
const captured = isRecord(fence) ? fence.credentialEpoch : undefined;
|
|
118
|
+
if (!isCredentialEpoch(captured))
|
|
119
|
+
throw refusal(operation, id, 'invalid-input', 'the credential epoch the provider state was read at is required');
|
|
120
|
+
const { row: seen } = await readRow(rt, operation, id);
|
|
121
|
+
await locks.acquire(rowLockSpec(rt, seen));
|
|
122
|
+
for (const extra of options.extraLocks ?? [])
|
|
123
|
+
await locks.acquire(extra);
|
|
124
|
+
return withTransaction(ctx, locks, progress, { operation, rowId: id }, async (tx) => {
|
|
125
|
+
const row = tx.row(id);
|
|
126
|
+
if (!row)
|
|
127
|
+
throw unknownRow(operation, id);
|
|
128
|
+
if (rowLockKey(row) !== rowLockKey(seen))
|
|
129
|
+
throw refusal(operation, id, 'row-key-changed', `row ${id}'s wire identity changed while its lock was being taken`, true);
|
|
130
|
+
if (row.invalid)
|
|
131
|
+
throw refusal(operation, id, 'invalid-row', `row ${id} failed validation`);
|
|
132
|
+
if (!row.credential)
|
|
133
|
+
throw refusal(operation, id, 'no-credential', `row ${id} holds no credential`);
|
|
134
|
+
requireBound(operation, row);
|
|
135
|
+
const epoch = row.credentialEpoch ?? 1;
|
|
136
|
+
if (epoch !== captured || row.identity !== fence.identity)
|
|
137
|
+
throw refusal(operation, id, 'attribution', `the provider state for ${id} was read for a credential or account the row no longer holds`, true);
|
|
138
|
+
const plan = await planProviderStateIn(tx, codec, operation, row, mutator);
|
|
139
|
+
if (plan.kind !== 'changed')
|
|
140
|
+
return {
|
|
141
|
+
id,
|
|
142
|
+
outcome: 'unchanged',
|
|
143
|
+
...(plan.kind === 'unchanged' && plan.value !== undefined
|
|
144
|
+
? { providerState: plan.value }
|
|
145
|
+
: {}),
|
|
146
|
+
};
|
|
147
|
+
tx.setStateAccount(id, plan.account);
|
|
148
|
+
await tx.commitState();
|
|
149
|
+
return plan.value === undefined
|
|
150
|
+
? { id, outcome: 'cleared' }
|
|
151
|
+
: { id, outcome: 'updated', providerState: plan.value };
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Returned by the provider-state mutator of an attributed `disable` or
|
|
157
|
+
* `enable` to decline the whole transition: nothing is written, neither the
|
|
158
|
+
* provider state nor the row's enabled flag, and the call resolves with
|
|
159
|
+
* `declined: true`. A mutator declines when the state it is shown is newer
|
|
160
|
+
* than what its caller saw, such as an eligibility recorded after the
|
|
161
|
+
* request whose refusal is being acted on. It is a value of its own because
|
|
162
|
+
* `undefined` already means "clear the provider state". `Symbol.for` keeps it
|
|
163
|
+
* equal across two copies of this module loaded in one process.
|
|
164
|
+
*/
|
|
165
|
+
export const DECLINE_TRANSITION = Symbol.for('@cortexkit/common-auth/store/decline-transition');
|
|
166
|
+
/**
|
|
167
|
+
* Runs a provider-state mutator for a row loaded under every lock and
|
|
168
|
+
* already checked by the caller (present, valid, inside its attribution
|
|
169
|
+
* fence), and plans the write. Refuses (`no-credential`) a row holding no
|
|
170
|
+
* credential, and (`unbound-credential`) one whose credential carries no
|
|
171
|
+
* stamp of this store at the row's epoch and identity: no stamp could bind
|
|
172
|
+
* the value, so no reader would ever show it. `DECLINE_TRANSITION` is
|
|
173
|
+
* honoured only when `declinable` is set; elsewhere it is not JSON and is
|
|
174
|
+
* refused as such.
|
|
175
|
+
*/
|
|
176
|
+
export async function planProviderStateIn(tx, codec, operation, row, mutator, declinable = false) {
|
|
177
|
+
const id = row.id;
|
|
178
|
+
const credential = row.credential;
|
|
179
|
+
if (!credential)
|
|
180
|
+
throw refusal(operation, id, 'no-credential', `row ${id} holds no credential`);
|
|
181
|
+
const epoch = row.credentialEpoch ?? 1;
|
|
182
|
+
const account = tx.stateAccount(id);
|
|
183
|
+
const rawStamp = account?.[CREDENTIAL_STAMP_KEY];
|
|
184
|
+
const stamp = parseStamp(rawStamp);
|
|
185
|
+
if (!isRecord(rawStamp) ||
|
|
186
|
+
!stamp?.binding ||
|
|
187
|
+
stamp.digest !== credentialDigest(credential) ||
|
|
188
|
+
stamp.credentialEpoch !== epoch ||
|
|
189
|
+
stamp.binding.identity !== row.identity)
|
|
190
|
+
throw refusal(operation, id, 'unbound-credential', `row ${id}'s credential carries no stamp of this store to bind a provider state to (stamp ${row.stamp}); rotate or replace it first`);
|
|
191
|
+
const current = row.providerState === undefined
|
|
192
|
+
? undefined
|
|
193
|
+
: structuredClone(row.providerState);
|
|
194
|
+
const returned = await runInsideHook(operation, () => mutator(current, row));
|
|
195
|
+
if (declinable && returned === DECLINE_TRANSITION)
|
|
196
|
+
return { kind: 'declined' };
|
|
197
|
+
const next = returned === undefined
|
|
198
|
+
? undefined
|
|
199
|
+
: acceptProviderState(codec, operation, id, returned, 'the provider state the mutator returned');
|
|
200
|
+
const held = account !== undefined && Object.hasOwn(account, PROVIDER_STATE_KEY);
|
|
201
|
+
if (next === undefined
|
|
202
|
+
? !held
|
|
203
|
+
: row.providerState !== undefined &&
|
|
204
|
+
JSON.stringify(row.providerState) === JSON.stringify(next))
|
|
205
|
+
return { kind: 'unchanged', ...(next !== undefined ? { value: next } : {}) };
|
|
206
|
+
const nextAccount = { ...account };
|
|
207
|
+
if (next === undefined) {
|
|
208
|
+
delete nextAccount[PROVIDER_STATE_KEY];
|
|
209
|
+
const nextStamp = { ...rawStamp };
|
|
210
|
+
delete nextStamp.providerState;
|
|
211
|
+
nextAccount[CREDENTIAL_STAMP_KEY] = nextStamp;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
nextAccount[PROVIDER_STATE_KEY] = next;
|
|
215
|
+
const digest = providerStateDigest(codec, next);
|
|
216
|
+
// A change confined to the part the codec does not bind to the
|
|
217
|
+
// credential leaves the stamp exactly as it was.
|
|
218
|
+
if (stamp.providerState !== digest)
|
|
219
|
+
nextAccount[CREDENTIAL_STAMP_KEY] = { ...rawStamp, providerState: digest };
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
kind: 'changed',
|
|
223
|
+
...(next !== undefined ? { value: next } : {}),
|
|
224
|
+
account: nextAccount,
|
|
225
|
+
};
|
|
226
|
+
}
|