@hollware/server 0.10.0 → 0.11.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/{ceremonies-DDoGdUD7.mjs → ceremonies-CI3DkOEL.mjs} +20 -6
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +1446 -364
- package/dist/{store-BPr9-wKy.mjs → store-DjA9s95l.mjs} +76 -2
- package/migrations/0015_site_changes_append_only.sql +28 -0
- package/migrations/0016_audit_log.sql +65 -0
- package/migrations/meta/0015_snapshot.json +2626 -0
- package/migrations/meta/0016_snapshot.json +2823 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as saveChallenge, b as userHandle, c as findInvitation, d as invitationById, h as redeemInvitation, i as createUser, l as findPasskey, m as passkeysOf, r as createSession, t as addPasskey, v as takeChallenge, y as touchPasskey } from "./store-DjA9s95l.mjs";
|
|
2
2
|
import { HollwareError, newId } from "@hollware/core";
|
|
3
3
|
import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse } from "@simplewebauthn/server";
|
|
4
4
|
import { isoBase64URL } from "@simplewebauthn/server/helpers";
|
|
@@ -15,6 +15,18 @@ function relyingParty(ceremony) {
|
|
|
15
15
|
function refused(message, fix) {
|
|
16
16
|
return new HollwareError("invalid_input", message, fix);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A verification of the library, or `{ verified: false }` when it throws: it throws for a response
|
|
20
|
+
* from another origin, without user verification, or with a counter that went back, and each of
|
|
21
|
+
* those is a passkey that could not be verified, not a failure of the server.
|
|
22
|
+
*/
|
|
23
|
+
async function unlessThrown(verify) {
|
|
24
|
+
try {
|
|
25
|
+
return await verify();
|
|
26
|
+
} catch {
|
|
27
|
+
return { verified: false };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
18
30
|
/** The options of `navigator.credentials.create` for the person of an invitation. */
|
|
19
31
|
async function registrationOptions(ceremony, invitationSecret) {
|
|
20
32
|
const invitation = await findInvitation(ceremony.db, invitationSecret, ceremony.now);
|
|
@@ -57,12 +69,13 @@ async function verifyRegistration(ceremony, input) {
|
|
|
57
69
|
});
|
|
58
70
|
if (challenge === null || challenge.invitationId === null || challenge.userId === null) throw refused("The registration expired.", "Start again from the link.");
|
|
59
71
|
const rp = relyingParty(ceremony);
|
|
60
|
-
const verified = await verifyRegistrationResponse({
|
|
72
|
+
const verified = await unlessThrown(() => verifyRegistrationResponse({
|
|
61
73
|
response: input.response,
|
|
62
74
|
expectedChallenge: challenge.challenge,
|
|
63
75
|
expectedOrigin: rp.origin,
|
|
64
|
-
expectedRPID: rp.id
|
|
65
|
-
|
|
76
|
+
expectedRPID: rp.id,
|
|
77
|
+
requireUserVerification: true
|
|
78
|
+
}));
|
|
66
79
|
if (!verified.verified) throw refused("The passkey could not be verified.", "Try again.");
|
|
67
80
|
const invitation = await invitationById(ceremony.db, challenge.invitationId);
|
|
68
81
|
if (invitation === null || !await redeemInvitation(ceremony.db, invitation.id, ceremony.now)) throw refused("This link is not valid: it was used already, or it expired.", "Ask the owner of this Hollware for a new invitation.");
|
|
@@ -122,18 +135,19 @@ async function verifyAuthentication(ceremony, input) {
|
|
|
122
135
|
const passkey = await findPasskey(ceremony.db, input.response.id);
|
|
123
136
|
if (passkey === null) throw refused("This passkey is not one of this Hollware.", "Use a passkey you registered here.");
|
|
124
137
|
const rp = relyingParty(ceremony);
|
|
125
|
-
const verified = await verifyAuthenticationResponse({
|
|
138
|
+
const verified = await unlessThrown(() => verifyAuthenticationResponse({
|
|
126
139
|
response: input.response,
|
|
127
140
|
expectedChallenge: challenge.challenge,
|
|
128
141
|
expectedOrigin: rp.origin,
|
|
129
142
|
expectedRPID: rp.id,
|
|
143
|
+
requireUserVerification: true,
|
|
130
144
|
credential: {
|
|
131
145
|
id: passkey.credentialId,
|
|
132
146
|
publicKey: isoBase64URL.toBuffer(passkey.publicKey),
|
|
133
147
|
counter: passkey.counter,
|
|
134
148
|
transports: JSON.parse(passkey.transports)
|
|
135
149
|
}
|
|
136
|
-
});
|
|
150
|
+
}));
|
|
137
151
|
if (!verified.verified) throw refused("The passkey could not be verified.", "Try again.");
|
|
138
152
|
await touchPasskey(ceremony.db, passkey.id, verified.authenticationInfo.newCounter, ceremony.now);
|
|
139
153
|
const session = await createSession(ceremony.db, {
|
package/dist/index.d.mts
CHANGED
|
@@ -663,6 +663,13 @@ export interface HollwareEnv {
|
|
|
663
663
|
* Personal tokens work without it; OAuth does not.
|
|
664
664
|
*/
|
|
665
665
|
readonly OAUTH_KV: KVNamespace;
|
|
666
|
+
/**
|
|
667
|
+
* A Workers Rate Limiting binding (`ratelimits` in wrangler.jsonc): with it, the routes anyone
|
|
668
|
+
* may call without a token (sign-in and passkeys, OAuth's token and registration, the beacon)
|
|
669
|
+
* answer 429 past its limit, per address and route (T-10 of docs/security/threat-model.md).
|
|
670
|
+
* Without it, nothing is limited.
|
|
671
|
+
*/
|
|
672
|
+
readonly HOLLWARE_RATE_LIMIT?: RateLimiter;
|
|
666
673
|
/** The static assets of the Worker, which hold the admin at `/admin/`; without them, no admin. */
|
|
667
674
|
readonly ASSETS?: Fetcher;
|
|
668
675
|
/** The Queue of publication events (ADR-025); without it, the cron trigger delivers them. */
|
|
@@ -706,6 +713,12 @@ export interface HollwareRpc {
|
|
|
706
713
|
readonly formats?: readonly ('markdown' | 'document' | 'html')[];
|
|
707
714
|
/** Also the published entries closest by meaning (ADR-030). */
|
|
708
715
|
readonly related?: boolean;
|
|
716
|
+
/**
|
|
717
|
+
* A preview token (`hw_prv_…`, made in the admin): the entry it names is read in its latest
|
|
718
|
+
* revision, a draft included. A token that does not hold (expired, of another site or
|
|
719
|
+
* entry, forged) is an `invalid_input` error, never the published version in its place.
|
|
720
|
+
*/
|
|
721
|
+
readonly preview?: string;
|
|
709
722
|
}): Promise<Output<typeof getEntry>>;
|
|
710
723
|
listEntries(site: string, collection: string, options?: {
|
|
711
724
|
readonly locale?: string;
|
|
@@ -750,5 +763,13 @@ export type CreateHollware = (config?: HollwareConfig) => HollwareWorker;
|
|
|
750
763
|
* The config is resolved once, here; a broken one throws its `ConfigError`.
|
|
751
764
|
*/
|
|
752
765
|
export declare const createHollware: CreateHollware;
|
|
766
|
+
/** What a Workers Rate Limiting binding answers: whether one more request for `key` fits. */
|
|
767
|
+
export interface RateLimiter {
|
|
768
|
+
limit(options: {
|
|
769
|
+
readonly key: string;
|
|
770
|
+
}): Promise<{
|
|
771
|
+
readonly success: boolean;
|
|
772
|
+
}>;
|
|
773
|
+
}
|
|
753
774
|
//#endregion
|
|
754
775
|
export type { Actor, AnalyticsReader, AnalyticsWriter, Clock, ContentStore, Embedder, EntrySummaryView, EntryView, EventMessage, Handlers, InTrash, ListQuery, NewRevision, OperationContext, OperationHandler, OutboxEvent, PageCount, PageView, Principal, PublishWrite, RelatedEntry, ReviewStatus, RevisionSummary, SearchHit, SearchQuery, Semantic, StoredEntry, StoredPublication, StoredReview, StoredReviewComment, StoredRevision, StoredSchedule, StoredStructure, StoredVector, Structure, StructureActor, StructureStore, VectorIndex, VectorMatch, VectorMetadata, View, WriteRefusal };
|