@cortexkit/common-auth 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/logger/engine.d.ts +24 -3
- package/dist/logger/engine.js +5 -1
- package/dist/logger/index.d.ts +1 -1
- package/dist/rpc/notifications.d.ts +25 -1
- package/dist/rpc/notifications.js +28 -6
- package/dist/rpc/port-file.d.ts +10 -1
- package/dist/rpc/port-file.js +50 -16
- package/dist/rpc/rpc-client.d.ts +19 -2
- package/dist/rpc/rpc-client.js +22 -7
- package/dist/rpc/rpc-server.d.ts +18 -0
- package/dist/rpc/rpc-server.js +98 -14
- package/dist/sidebar-file/sidebar-file.d.ts +10 -0
- package/dist/sidebar-file/sidebar-file.js +1 -0
- package/dist/store/attribution.d.ts +13 -2
- package/dist/store/identity.d.ts +5 -0
- package/dist/store/identity.js +16 -0
- package/dist/store/index.d.ts +3 -2
- package/dist/store/index.js +1 -0
- package/dist/store/mutate.d.ts +5 -1
- package/dist/store/mutate.js +14 -2
- package/dist/store/pool.d.ts +15 -9
- package/dist/store/provider-state.d.ts +45 -0
- package/dist/store/provider-state.js +80 -43
- package/dist/store/rows.d.ts +47 -11
- package/dist/store/rows.js +147 -56
- package/dist/store/schema.d.ts +45 -3
- package/dist/store/schema.js +87 -3
- package/dist/store/torn.d.ts +29 -0
- package/dist/store/torn.js +72 -1
- package/package.json +1 -1
package/dist/store/identity.js
CHANGED
|
@@ -24,6 +24,22 @@ export function disableIn(tx, id, reason) {
|
|
|
24
24
|
const entry = tx.entry(id) ?? { credentialEpoch: 1, needsFirstReading: true };
|
|
25
25
|
tx.setEntry(id, { ...entry, disabledReason: reason });
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Marks a row enabled: `enabled: true` in the roster row and no
|
|
29
|
+
* `disabledReason` in its entry. A row without an entry is not given one.
|
|
30
|
+
*/
|
|
31
|
+
export function enableIn(tx, id) {
|
|
32
|
+
const raw = tx.rosterRow(id);
|
|
33
|
+
if (!raw)
|
|
34
|
+
return;
|
|
35
|
+
raw.enabled = true;
|
|
36
|
+
const entry = tx.entry(id);
|
|
37
|
+
if (entry && 'disabledReason' in entry) {
|
|
38
|
+
const next = { ...entry };
|
|
39
|
+
delete next.disabledReason;
|
|
40
|
+
tx.setEntry(id, next);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
27
43
|
/**
|
|
28
44
|
* Two enabled OAuth rows with one wire identity are the same account: the
|
|
29
45
|
* earlier row in roster order stays enabled and every later one is disabled
|
package/dist/store/index.d.ts
CHANGED
|
@@ -6,12 +6,13 @@ export { countUnknownIdentityRows, DUPLICATE_IDENTITY_REASON, } from './identity
|
|
|
6
6
|
export type { HoldPoint, InitializeOutcome, WriteStep } from './mutate.js';
|
|
7
7
|
export type { OpenPoolStoreOptions, PoolLoad, PoolStore, } from './pool.js';
|
|
8
8
|
export { openPoolStore } from './pool.js';
|
|
9
|
-
export type { ProviderStateMutator, UpdateProviderStateResult, } from './provider-state.js';
|
|
9
|
+
export type { ProviderStateMutator, RowTransitionMutator, UpdateProviderStateResult, } from './provider-state.js';
|
|
10
|
+
export { DECLINE_TRANSITION } from './provider-state.js';
|
|
10
11
|
export type { PullHook, PullRequest } from './pull.js';
|
|
11
12
|
export type { ProviderRefresh, ProviderRefreshResult, RefreshOptions, RefreshOutcome, } from './refresh.js';
|
|
12
13
|
export type { LockEvent, PoolLockOptions, PoolLockSpec, } from './refresh-lock.js';
|
|
13
14
|
export { POOL_LOCK_DEFAULTS } from './refresh-lock.js';
|
|
14
|
-
export type { AddInput, AddResult, CredentialWriteInput, FailureHook, RemoveOptions, RemoveResult, RemoveView, ReorderOptions, ReorderResult, RowOperationOptions, RowToggleOptions, } from './rows.js';
|
|
15
|
+
export type { AddInput, AddResult, CredentialWriteInput, FailureHook, RemoveOptions, RemoveResult, RemoveView, ReorderOptions, ReorderResult, RowOperationOptions, RowToggleOptions, RowTransitionOptions, RowTransitionResult, } from './rows.js';
|
|
15
16
|
export type { PullReason } from './runtime.js';
|
|
16
17
|
export type { ApiKeyCredential, CredentialStampStatus, OAuthCredential, PoolCredential, PoolRow, ProviderStateCodec, ProviderStateDrop, ProviderStateReplacement, QuotaCodec, RotateCredential, StoredCredential, } from './schema.js';
|
|
17
18
|
export { fingerprintOf, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, PROVIDER_STATE_KEY, REFRESH_STAMP_TOLERANCE_MS, rowLockKey, } from './schema.js';
|
package/dist/store/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { PoolOperationError, PoolReentryError } from './errors.js';
|
|
2
2
|
export { countUnknownIdentityRows, DUPLICATE_IDENTITY_REASON, } from './identity.js';
|
|
3
3
|
export { openPoolStore } from './pool.js';
|
|
4
|
+
export { DECLINE_TRANSITION } from './provider-state.js';
|
|
4
5
|
export { POOL_LOCK_DEFAULTS } from './refresh-lock.js';
|
|
5
6
|
export { fingerprintOf, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, PROVIDER_STATE_KEY, REFRESH_STAMP_TOLERANCE_MS, rowLockKey, } from './schema.js';
|
|
6
7
|
export { POOL_OWNED_KEYS } from './settings.js';
|
package/dist/store/mutate.d.ts
CHANGED
|
@@ -118,7 +118,11 @@ export declare class Transaction {
|
|
|
118
118
|
* Writes the config: legacy `version: 1` and the legacy roster beside
|
|
119
119
|
* `commonAuthPool`, every other top-level key and every unrecognised pool
|
|
120
120
|
* key untouched. Entries for ids no longer in the roster are dropped here,
|
|
121
|
-
* and remembered so the id is not reused in this process.
|
|
121
|
+
* and remembered so the id is not reused in this process. Every id the
|
|
122
|
+
* write drops (a roster row the files held when the transaction read them,
|
|
123
|
+
* or an entry left without one) has its epoch recorded in the config (see
|
|
124
|
+
* `retireEpochsIn`), which is what keeps a later `add` of the id, from any
|
|
125
|
+
* process, past every epoch an attribution could name.
|
|
122
126
|
*/
|
|
123
127
|
commitConfig(options?: {
|
|
124
128
|
counted?: boolean;
|
package/dist/store/mutate.js
CHANGED
|
@@ -4,7 +4,7 @@ import { LockContentionError, LockOwnershipError } from '../fs/with-lock.js';
|
|
|
4
4
|
import { PoolOperationError, } from './errors.js';
|
|
5
5
|
import { callFailureHook } from './hooks.js';
|
|
6
6
|
import { LockStack, } from './refresh-lock.js';
|
|
7
|
-
import { classifyConfig, classifyState, ensureEntries, entryIn, isRecord, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, rosterRowIn, setEntryIn, } from './schema.js';
|
|
7
|
+
import { classifyConfig, classifyState, ensureEntries, entryIn, isRecord, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, retireEpochsIn, rosterOf, rosterRowIn, setEntryIn, } from './schema.js';
|
|
8
8
|
import { completeTornRows, loadRows } from './torn.js';
|
|
9
9
|
async function readJson(path) {
|
|
10
10
|
let text;
|
|
@@ -169,7 +169,11 @@ export class Transaction {
|
|
|
169
169
|
* Writes the config: legacy `version: 1` and the legacy roster beside
|
|
170
170
|
* `commonAuthPool`, every other top-level key and every unrecognised pool
|
|
171
171
|
* key untouched. Entries for ids no longer in the roster are dropped here,
|
|
172
|
-
* and remembered so the id is not reused in this process.
|
|
172
|
+
* and remembered so the id is not reused in this process. Every id the
|
|
173
|
+
* write drops (a roster row the files held when the transaction read them,
|
|
174
|
+
* or an entry left without one) has its epoch recorded in the config (see
|
|
175
|
+
* `retireEpochsIn`), which is what keeps a later `add` of the id, from any
|
|
176
|
+
* process, past every epoch an attribution could name.
|
|
173
177
|
*/
|
|
174
178
|
async commitConfig(options = {}) {
|
|
175
179
|
const roster = this.roster();
|
|
@@ -177,6 +181,14 @@ export class Transaction {
|
|
|
177
181
|
for (const raw of roster)
|
|
178
182
|
if (isRecord(raw) && typeof raw.id === 'string')
|
|
179
183
|
rosterIds.add(raw.id);
|
|
184
|
+
const dropped = new Set();
|
|
185
|
+
for (const raw of rosterOf(this.snapshot.config))
|
|
186
|
+
if (isRecord(raw) && typeof raw.id === 'string' && !rosterIds.has(raw.id))
|
|
187
|
+
dropped.add(raw.id);
|
|
188
|
+
for (const id of Object.keys(this.entries()))
|
|
189
|
+
if (!rosterIds.has(id))
|
|
190
|
+
dropped.add(id);
|
|
191
|
+
retireEpochsIn(this.config, dropped);
|
|
180
192
|
const entries = this.entries();
|
|
181
193
|
const kept = {};
|
|
182
194
|
for (const [id, entry] of Object.entries(entries)) {
|
package/dist/store/pool.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { type ProviderStateMutator, type UpdateProviderStateResult } from './pro
|
|
|
6
6
|
import { type PullHook } from './pull.js';
|
|
7
7
|
import { type ProviderRefresh, type RefreshOptions, type RefreshOutcome } from './refresh.js';
|
|
8
8
|
import { type LockEnvironment, type PoolLockOptions, type PoolLockSpec } from './refresh-lock.js';
|
|
9
|
-
import { type AddInput, type AddResult, type CredentialWriteInput, type RemoveOptions, type RemoveResult, type ReorderOptions, type ReorderResult, type RowOperationOptions, type RowToggleOptions } from './rows.js';
|
|
9
|
+
import { type AddInput, type AddResult, type CredentialWriteInput, type RemoveOptions, type RemoveResult, type ReorderOptions, type ReorderResult, type RowOperationOptions, type RowToggleOptions, type RowTransitionOptions, type RowTransitionResult } from './rows.js';
|
|
10
10
|
import { type PoolCredential, type PoolRow, type ProviderStateCodec, type QuotaCodec, type RotateCredential, type StoredCredential } from './schema.js';
|
|
11
11
|
import { type SettingsMutator, type SettingsRead, type UpdateSettingsOptions, type UpdateSettingsResult } from './settings.js';
|
|
12
12
|
export interface OpenPoolStoreOptions {
|
|
@@ -88,6 +88,13 @@ export interface PoolStore {
|
|
|
88
88
|
}): Promise<{
|
|
89
89
|
status: InitializeOutcome;
|
|
90
90
|
}>;
|
|
91
|
+
/**
|
|
92
|
+
* Adds a row, or completes or rotates the row already holding the id or
|
|
93
|
+
* the secret. A new row starts at credential epoch 1; since 0.8.0 one
|
|
94
|
+
* whose id the pool held before starts one past the highest epoch that id
|
|
95
|
+
* held (see `Attribution`), and an id that held `Number.MAX_SAFE_INTEGER`
|
|
96
|
+
* refuses (`id-removed`) before writing.
|
|
97
|
+
*/
|
|
91
98
|
add(input: AddInput, options?: RowOperationOptions): Promise<AddResult>;
|
|
92
99
|
/**
|
|
93
100
|
* Gives a row a new credential and a new credential epoch. Since 0.6.0 the
|
|
@@ -119,19 +126,18 @@ export interface PoolStore {
|
|
|
119
126
|
/**
|
|
120
127
|
* Sets `enabled: false` and the entry's `disabledReason`. Takes the row
|
|
121
128
|
* lock, then `extraLocks`, then the store locks (the row lock and
|
|
122
|
-
* `extraLocks` since 0.2.3).
|
|
129
|
+
* `extraLocks` since 0.2.3). Since 0.7.0 it may be fenced on the
|
|
130
|
+
* credential the caller's evidence is about (`attribution`) and carry a
|
|
131
|
+
* provider-state change that lands with it (`providerState`); see
|
|
132
|
+
* `RowTransitionOptions`.
|
|
123
133
|
*/
|
|
124
|
-
disable(id: string, reason: string, options?:
|
|
125
|
-
id: string;
|
|
126
|
-
}>;
|
|
134
|
+
disable(id: string, reason: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
|
|
127
135
|
/**
|
|
128
136
|
* Clears `enabled: false` and `disabledReason` (since 0.2.3); refuses with
|
|
129
137
|
* `duplicate-identity` when another enabled OAuth row holds the row's
|
|
130
|
-
* identity. Locks as `disable
|
|
138
|
+
* identity. Locks as `disable`, and takes the same options since 0.7.0.
|
|
131
139
|
*/
|
|
132
|
-
enable(id: string, options?:
|
|
133
|
-
id: string;
|
|
134
|
-
}>;
|
|
140
|
+
enable(id: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
|
|
135
141
|
/**
|
|
136
142
|
* Deletes the roster row, its per-row entry and its state-file credential
|
|
137
143
|
* (since 0.2.3). Locks as `disable`; `protect` can refuse the id.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Attribution } from './attribution.js';
|
|
2
2
|
import type { PoolOperation } from './errors.js';
|
|
3
|
+
import { type Transaction } from './mutate.js';
|
|
3
4
|
import type { RowToggleOptions } from './rows.js';
|
|
4
5
|
import { type StoreRuntime } from './runtime.js';
|
|
5
6
|
import { type PoolRow, type ProviderStateCodec } from './schema.js';
|
|
@@ -84,3 +85,47 @@ export type UpdateProviderStateResult = {
|
|
|
84
85
|
* `replace` stamps such a row.
|
|
85
86
|
*/
|
|
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>;
|
|
@@ -132,55 +132,92 @@ export async function updateProviderStateRow(rt, id, fence, mutator, options = {
|
|
|
132
132
|
const epoch = row.credentialEpoch ?? 1;
|
|
133
133
|
if (epoch !== captured || row.identity !== fence.identity)
|
|
134
134
|
throw refusal(operation, id, 'attribution', `the provider state for ${id} was read for a credential or account the row no longer holds`, true);
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
const stamp = parseStamp(rawStamp);
|
|
138
|
-
if (!isRecord(rawStamp) ||
|
|
139
|
-
!stamp?.binding ||
|
|
140
|
-
stamp.digest !== credentialDigest(row.credential) ||
|
|
141
|
-
stamp.credentialEpoch !== epoch ||
|
|
142
|
-
stamp.binding.identity !== row.identity)
|
|
143
|
-
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`);
|
|
144
|
-
const current = row.providerState === undefined
|
|
145
|
-
? undefined
|
|
146
|
-
: structuredClone(row.providerState);
|
|
147
|
-
const returned = await runInsideHook(operation, () => mutator(current, row));
|
|
148
|
-
const next = returned === undefined
|
|
149
|
-
? undefined
|
|
150
|
-
: acceptProviderState(codec, operation, id, returned, 'the provider state the mutator returned');
|
|
151
|
-
const held = account !== undefined && Object.hasOwn(account, PROVIDER_STATE_KEY);
|
|
152
|
-
if (next === undefined
|
|
153
|
-
? !held
|
|
154
|
-
: row.providerState !== undefined &&
|
|
155
|
-
JSON.stringify(row.providerState) === JSON.stringify(next))
|
|
135
|
+
const plan = await planProviderStateIn(tx, codec, operation, row, mutator);
|
|
136
|
+
if (plan.kind !== 'changed')
|
|
156
137
|
return {
|
|
157
138
|
id,
|
|
158
139
|
outcome: 'unchanged',
|
|
159
|
-
...(
|
|
140
|
+
...(plan.kind === 'unchanged' && plan.value !== undefined
|
|
141
|
+
? { providerState: plan.value }
|
|
142
|
+
: {}),
|
|
160
143
|
};
|
|
161
|
-
|
|
162
|
-
if (next === undefined) {
|
|
163
|
-
delete nextAccount[PROVIDER_STATE_KEY];
|
|
164
|
-
const nextStamp = { ...rawStamp };
|
|
165
|
-
delete nextStamp.providerState;
|
|
166
|
-
nextAccount[CREDENTIAL_STAMP_KEY] = nextStamp;
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
nextAccount[PROVIDER_STATE_KEY] = next;
|
|
170
|
-
const digest = providerStateDigest(codec, next);
|
|
171
|
-
// A change confined to the part the codec does not bind to the
|
|
172
|
-
// credential leaves the stamp exactly as it was.
|
|
173
|
-
if (stamp.providerState !== digest)
|
|
174
|
-
nextAccount[CREDENTIAL_STAMP_KEY] = {
|
|
175
|
-
...rawStamp,
|
|
176
|
-
providerState: digest,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
tx.setStateAccount(id, nextAccount);
|
|
144
|
+
tx.setStateAccount(id, plan.account);
|
|
180
145
|
await tx.commitState();
|
|
181
|
-
return
|
|
146
|
+
return plan.value === undefined
|
|
182
147
|
? { id, outcome: 'cleared' }
|
|
183
|
-
: { id, outcome: 'updated', providerState:
|
|
148
|
+
: { id, outcome: 'updated', providerState: plan.value };
|
|
184
149
|
});
|
|
185
150
|
});
|
|
186
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Returned by the provider-state mutator of an attributed `disable` or
|
|
154
|
+
* `enable` to decline the whole transition: nothing is written, neither the
|
|
155
|
+
* provider state nor the row's enabled flag, and the call resolves with
|
|
156
|
+
* `declined: true`. A mutator declines when the state it is shown is newer
|
|
157
|
+
* than what its caller saw, such as an eligibility recorded after the
|
|
158
|
+
* request whose refusal is being acted on. It is a value of its own because
|
|
159
|
+
* `undefined` already means "clear the provider state". `Symbol.for` keeps it
|
|
160
|
+
* equal across two copies of this module loaded in one process.
|
|
161
|
+
*/
|
|
162
|
+
export const DECLINE_TRANSITION = Symbol.for('@cortexkit/common-auth/store/decline-transition');
|
|
163
|
+
/**
|
|
164
|
+
* Runs a provider-state mutator for a row loaded under every lock and
|
|
165
|
+
* already checked by the caller (present, valid, inside its attribution
|
|
166
|
+
* fence), and plans the write. Refuses (`no-credential`) a row holding no
|
|
167
|
+
* credential, and (`unbound-credential`) one whose credential carries no
|
|
168
|
+
* stamp of this store at the row's epoch and identity: no stamp could bind
|
|
169
|
+
* the value, so no reader would ever show it. `DECLINE_TRANSITION` is
|
|
170
|
+
* honoured only when `declinable` is set; elsewhere it is not JSON and is
|
|
171
|
+
* refused as such.
|
|
172
|
+
*/
|
|
173
|
+
export async function planProviderStateIn(tx, codec, operation, row, mutator, declinable = false) {
|
|
174
|
+
const id = row.id;
|
|
175
|
+
const credential = row.credential;
|
|
176
|
+
if (!credential)
|
|
177
|
+
throw refusal(operation, id, 'no-credential', `row ${id} holds no credential`);
|
|
178
|
+
const epoch = row.credentialEpoch ?? 1;
|
|
179
|
+
const account = tx.stateAccount(id);
|
|
180
|
+
const rawStamp = account?.[CREDENTIAL_STAMP_KEY];
|
|
181
|
+
const stamp = parseStamp(rawStamp);
|
|
182
|
+
if (!isRecord(rawStamp) ||
|
|
183
|
+
!stamp?.binding ||
|
|
184
|
+
stamp.digest !== credentialDigest(credential) ||
|
|
185
|
+
stamp.credentialEpoch !== epoch ||
|
|
186
|
+
stamp.binding.identity !== row.identity)
|
|
187
|
+
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`);
|
|
188
|
+
const current = row.providerState === undefined
|
|
189
|
+
? undefined
|
|
190
|
+
: structuredClone(row.providerState);
|
|
191
|
+
const returned = await runInsideHook(operation, () => mutator(current, row));
|
|
192
|
+
if (declinable && returned === DECLINE_TRANSITION)
|
|
193
|
+
return { kind: 'declined' };
|
|
194
|
+
const next = returned === undefined
|
|
195
|
+
? undefined
|
|
196
|
+
: acceptProviderState(codec, operation, id, returned, 'the provider state the mutator returned');
|
|
197
|
+
const held = account !== undefined && Object.hasOwn(account, PROVIDER_STATE_KEY);
|
|
198
|
+
if (next === undefined
|
|
199
|
+
? !held
|
|
200
|
+
: row.providerState !== undefined &&
|
|
201
|
+
JSON.stringify(row.providerState) === JSON.stringify(next))
|
|
202
|
+
return { kind: 'unchanged', ...(next !== undefined ? { value: next } : {}) };
|
|
203
|
+
const nextAccount = { ...account };
|
|
204
|
+
if (next === undefined) {
|
|
205
|
+
delete nextAccount[PROVIDER_STATE_KEY];
|
|
206
|
+
const nextStamp = { ...rawStamp };
|
|
207
|
+
delete nextStamp.providerState;
|
|
208
|
+
nextAccount[CREDENTIAL_STAMP_KEY] = nextStamp;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
nextAccount[PROVIDER_STATE_KEY] = next;
|
|
212
|
+
const digest = providerStateDigest(codec, next);
|
|
213
|
+
// A change confined to the part the codec does not bind to the
|
|
214
|
+
// credential leaves the stamp exactly as it was.
|
|
215
|
+
if (stamp.providerState !== digest)
|
|
216
|
+
nextAccount[CREDENTIAL_STAMP_KEY] = { ...rawStamp, providerState: digest };
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
kind: 'changed',
|
|
220
|
+
...(next !== undefined ? { value: next } : {}),
|
|
221
|
+
account: nextAccount,
|
|
222
|
+
};
|
|
223
|
+
}
|
package/dist/store/rows.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Attribution } from './attribution.js';
|
|
2
2
|
import { PoolOperationError } from './errors.js';
|
|
3
3
|
import { type Transaction } from './mutate.js';
|
|
4
|
-
import { type ProviderStateWrite } from './provider-state.js';
|
|
4
|
+
import { type ProviderStateWrite, type RowTransitionMutator, type UpdateProviderStateResult } from './provider-state.js';
|
|
5
5
|
import type { PoolLockSpec } from './refresh-lock.js';
|
|
6
6
|
import { type StoreRuntime } from './runtime.js';
|
|
7
7
|
import { type CredentialBinding, type PoolCredential, type PoolRow, type RotateCredential, type StoredCredential } from './schema.js';
|
|
@@ -134,23 +134,57 @@ export declare function rotateRow(rt: StoreRuntime, id: string, credential: Rota
|
|
|
134
134
|
credential: StoredCredential;
|
|
135
135
|
}>;
|
|
136
136
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* for a refresh of the row instead of landing during its provider call.
|
|
137
|
+
* Options of `disable` and `enable`. A call that passes neither
|
|
138
|
+
* `attribution` nor `providerState` behaves exactly as it did before 0.7.0.
|
|
140
139
|
*/
|
|
141
|
-
export
|
|
140
|
+
export interface RowTransitionOptions extends RowToggleOptions {
|
|
141
|
+
/**
|
|
142
|
+
* The credential epoch and recorded identity the caller's evidence for the
|
|
143
|
+
* transition was obtained under (as `recordQuota`'s attribution: an
|
|
144
|
+
* identity left out means the row had none). The call is refused
|
|
145
|
+
* (`attribution`, retryable, nothing written) once the row holds another
|
|
146
|
+
* epoch or identity, so a provider's late answer about a replaced
|
|
147
|
+
* credential never disables, or switches back on, the row now holding its
|
|
148
|
+
* successor.
|
|
149
|
+
*/
|
|
150
|
+
attribution?: Attribution;
|
|
151
|
+
/**
|
|
152
|
+
* A provider-state change made in the same transaction as the transition,
|
|
153
|
+
* under the rules of `updateProviderState` (codec validation, the stamp
|
|
154
|
+
* rebound to the value, `unbound-credential` for a row no stamp of this
|
|
155
|
+
* store can bind it to); it requires `attribution`. The value and the
|
|
156
|
+
* enabled flag land together: no reader, and no crash at any write point,
|
|
157
|
+
* shows one without the other. Returning `DECLINE_TRANSITION` declines the
|
|
158
|
+
* whole call and writes nothing.
|
|
159
|
+
*/
|
|
160
|
+
providerState?: RowTransitionMutator;
|
|
161
|
+
}
|
|
162
|
+
export interface RowTransitionResult {
|
|
142
163
|
id: string;
|
|
143
|
-
|
|
164
|
+
/** The provider-state mutator declined: nothing was written. */
|
|
165
|
+
declined?: true;
|
|
166
|
+
/**
|
|
167
|
+
* Set when a provider-state mutator ran and did not decline: what it did
|
|
168
|
+
* to the value, as `updateProviderState` reports it.
|
|
169
|
+
*/
|
|
170
|
+
providerStateOutcome?: UpdateProviderStateResult['outcome'];
|
|
171
|
+
/** The provider state the row now holds, when a mutator ran and left one. */
|
|
172
|
+
providerState?: unknown;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Marks a row disabled with a reason. See `RowTransitionOptions` for the
|
|
176
|
+
* attributed form, which may change the provider state with it.
|
|
177
|
+
*/
|
|
178
|
+
export declare function disableRow(rt: StoreRuntime, id: string, reason: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
|
|
144
179
|
/**
|
|
145
180
|
* Clears a row's `enabled: false` and its `disabledReason` in one config
|
|
146
181
|
* write. An OAuth row whose recorded identity another enabled OAuth row holds
|
|
147
182
|
* stays disabled and the call refuses (`duplicate-identity`): the same rule
|
|
148
183
|
* that makes `add` store such a row disabled. Enabling a row that is already
|
|
149
|
-
* enabled writes nothing.
|
|
184
|
+
* enabled writes nothing. See `RowTransitionOptions` for the attributed
|
|
185
|
+
* form, which may change the provider state with it.
|
|
150
186
|
*/
|
|
151
|
-
export declare function enableRow(rt: StoreRuntime, id: string, options?:
|
|
152
|
-
id: string;
|
|
153
|
-
}>;
|
|
187
|
+
export declare function enableRow(rt: StoreRuntime, id: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
|
|
154
188
|
/**
|
|
155
189
|
* Deletes a row: its roster row and per-row entry (quota, epoch; the identity
|
|
156
190
|
* lives in the roster row) in one config write, then its credential and
|
|
@@ -158,7 +192,9 @@ export declare function enableRow(rt: StoreRuntime, id: string, options?: RowTog
|
|
|
158
192
|
* between the two leaves a row every reader already sees as removed, with
|
|
159
193
|
* only an orphaned state entry that no reader loads; calling `remove` again
|
|
160
194
|
* drops that entry (`completed`). As with every id the store drops, the id is
|
|
161
|
-
* not reused by `add` in this process
|
|
195
|
+
* not reused by `add` in this process, and the config write records the
|
|
196
|
+
* row's credential epoch, so an `add` of the id in any other process starts
|
|
197
|
+
* past it (see `nextAddEpochIn`).
|
|
162
198
|
*/
|
|
163
199
|
export declare function removeRow(rt: StoreRuntime, id: string, options?: RemoveOptions): Promise<RemoveResult>;
|
|
164
200
|
/**
|