@fungi.computer/seam 0.2.1 → 0.2.3
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/index.d.ts +18 -1
- package/dist/index.js +60 -14
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -66,7 +66,24 @@ export type SeamReplayStore = Readonly<{
|
|
|
66
66
|
export declare class SeamReplayGuard implements SeamReplayStore {
|
|
67
67
|
#private;
|
|
68
68
|
/** Record one nonce, refusing capacity rather than evicting live history. */
|
|
69
|
-
admit(
|
|
69
|
+
admit(record: SeamReplayRecord): SeamReplayAdmission;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Process-local replay history split into independent bounded partitions, one
|
|
73
|
+
* per sender-controlled traffic source (for example one VM behind a shared
|
|
74
|
+
* host). A source that exhausts its own partition is refused there and never
|
|
75
|
+
* evicts or fills another partition. The partition key must be covered by the
|
|
76
|
+
* request signature: a partition is created only by `admit`, which
|
|
77
|
+
* {@link verifySeamRequest} calls after the signature verified, so unsigned
|
|
78
|
+
* traffic allocates nothing. Partitions whose history pruned to empty are
|
|
79
|
+
* dropped before a new one is refused at the partition ceiling.
|
|
80
|
+
*/
|
|
81
|
+
export declare class SeamReplayPartitions {
|
|
82
|
+
#private;
|
|
83
|
+
/** `maximumPartitions` is the number of distinct sources that may be live at once. */
|
|
84
|
+
constructor(maximumPartitions: number);
|
|
85
|
+
/** The replay store for one source; nothing is allocated before it admits. */
|
|
86
|
+
partition(key: string): SeamReplayStore;
|
|
70
87
|
}
|
|
71
88
|
/**
|
|
72
89
|
* Verify one request's seam header. `digest` is the receiver's own digest of
|
package/dist/index.js
CHANGED
|
@@ -109,22 +109,68 @@ export async function signSeamRequest(signer, audience, method, url, digest, now
|
|
|
109
109
|
export class SeamReplayGuard {
|
|
110
110
|
#seen = new Map();
|
|
111
111
|
/** Record one nonce, refusing capacity rather than evicting live history. */
|
|
112
|
-
admit(
|
|
113
|
-
this.#
|
|
114
|
-
if (this.#seen.has(nonce))
|
|
115
|
-
return "replayed";
|
|
116
|
-
if (this.#seen.size >= maximumNonces)
|
|
117
|
-
return "at-capacity";
|
|
118
|
-
this.#seen.set(nonce, expiresAt);
|
|
119
|
-
return "admitted";
|
|
112
|
+
admit(record) {
|
|
113
|
+
return admitNonce(this.#seen, record);
|
|
120
114
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Process-local replay history split into independent bounded partitions, one
|
|
118
|
+
* per sender-controlled traffic source (for example one VM behind a shared
|
|
119
|
+
* host). A source that exhausts its own partition is refused there and never
|
|
120
|
+
* evicts or fills another partition. The partition key must be covered by the
|
|
121
|
+
* request signature: a partition is created only by `admit`, which
|
|
122
|
+
* {@link verifySeamRequest} calls after the signature verified, so unsigned
|
|
123
|
+
* traffic allocates nothing. Partitions whose history pruned to empty are
|
|
124
|
+
* dropped before a new one is refused at the partition ceiling.
|
|
125
|
+
*/
|
|
126
|
+
export class SeamReplayPartitions {
|
|
127
|
+
#partitions = new Map();
|
|
128
|
+
#maximumPartitions;
|
|
129
|
+
/** `maximumPartitions` is the number of distinct sources that may be live at once. */
|
|
130
|
+
constructor(maximumPartitions) {
|
|
131
|
+
if (!Number.isSafeInteger(maximumPartitions) || maximumPartitions < 1)
|
|
132
|
+
throw new RangeError("Seam replay partitions need a positive ceiling");
|
|
133
|
+
this.#maximumPartitions = maximumPartitions;
|
|
134
|
+
}
|
|
135
|
+
/** The replay store for one source; nothing is allocated before it admits. */
|
|
136
|
+
partition(key) {
|
|
137
|
+
return { admit: (record) => this.#admit(key, record) };
|
|
138
|
+
}
|
|
139
|
+
#admit(key, record) {
|
|
140
|
+
let seen = this.#partitions.get(key);
|
|
141
|
+
if (seen === undefined) {
|
|
142
|
+
if (this.#partitions.size >= this.#maximumPartitions)
|
|
143
|
+
this.#dropEmpty(record.now);
|
|
144
|
+
if (this.#partitions.size >= this.#maximumPartitions)
|
|
145
|
+
return "at-capacity";
|
|
146
|
+
seen = new Map();
|
|
147
|
+
this.#partitions.set(key, seen);
|
|
127
148
|
}
|
|
149
|
+
return admitNonce(seen, record);
|
|
150
|
+
}
|
|
151
|
+
#dropEmpty(now) {
|
|
152
|
+
for (const [key, seen] of this.#partitions) {
|
|
153
|
+
pruneNonces(seen, now);
|
|
154
|
+
if (seen.size === 0)
|
|
155
|
+
this.#partitions.delete(key);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function admitNonce(seen, { nonce, now, expiresAt }) {
|
|
160
|
+
pruneNonces(seen, now);
|
|
161
|
+
if (seen.has(nonce))
|
|
162
|
+
return "replayed";
|
|
163
|
+
if (seen.size >= maximumNonces)
|
|
164
|
+
return "at-capacity";
|
|
165
|
+
seen.set(nonce, expiresAt);
|
|
166
|
+
return "admitted";
|
|
167
|
+
}
|
|
168
|
+
function pruneNonces(seen, now) {
|
|
169
|
+
for (const [nonce, expires] of seen) {
|
|
170
|
+
// Freshness accepts the boundary instant. Clock steps also mean Map
|
|
171
|
+
// insertion order need not be expiry order: inspect every bounded entry.
|
|
172
|
+
if (expires < now)
|
|
173
|
+
seen.delete(nonce);
|
|
128
174
|
}
|
|
129
175
|
}
|
|
130
176
|
function seamFields(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/seam",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Ed25519 authentication between Fungi Hub and on-box hosts",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@fungi.computer/carrier": "0.2.
|
|
19
|
+
"@fungi.computer/carrier": "0.2.2",
|
|
20
20
|
"effect": "4.0.2"
|
|
21
21
|
},
|
|
22
22
|
"license": "MIT",
|