@hollware/server 0.10.0 → 1.0.0-rc.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.
@@ -1,4 +1,4 @@
1
- import { a as findInvitation, c as invitationById, d as redeemInvitation, f as saveChallenge, h as userHandle, i as createUser, m as touchPasskey, o as findPasskey, p as takeChallenge, r as createSession, t as addPasskey, u as passkeysOf } from "./store-BPr9-wKy.mjs";
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 };