@ccmsg/protocol 2.3.0 → 2.4.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.
@@ -72,24 +72,13 @@ function authority(tail: string): string {
72
72
  * carries no query or fragment. */
73
73
  const BASE_URL = authority("(?:/[^?#\\s]*)?/$");
74
74
 
75
- /** The path part of a base URL, as above. */
76
- const BASE_PATH = "(?:/[^?#\\s]*)?/$";
77
-
78
- /** A base URL a WebAuthn ceremony can actually run at.
79
- *
80
- * Narrower than `BASE_URL` on two counts, both of them the authenticator's
81
- * rules rather than this contract's taste. A ceremony needs a secure context,
82
- * so the scheme is `https` — with `http` on the loopback names browsers treat
83
- * as trustworthy, which is what makes a web UI runnable on a development
84
- * machine. And a relying party is a domain, so the host may not be an address
85
- * literal: `https://198.51.100.9/` parses fine and could never hold a passkey.
86
- *
87
- * Writing it into the type rather than leaving it to the daemon is what keeps
88
- * `rpIdOf` total over the values a record may carry. A URL that no ceremony can
89
- * run at would be a credential that could never have been made. */
90
- const WEBUI_URL =
91
- `^(?:https://(?!\\d{1,3}(?:\\.\\d{1,3}){3}(?:[:/]))${HOST_NAME}${port("443")}` +
92
- `|http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])${port("80")})${BASE_PATH}`;
75
+ /** An origin a WebAuthn ceremony can be held at: `https` on a host that is a
76
+ * domain, or `http` on one of the loopback names a browser trusts. The
77
+ * lookahead is what keeps an IPv4 literal out, a bracketed IPv6 one being
78
+ * outside `HOST_NAME` already. */
79
+ const CEREMONY_ORIGIN =
80
+ `^(?:https://(?!\\d{1,3}(?:\\.\\d{1,3}){3}(?::|$))${HOST_NAME}${port("443")}` +
81
+ `|http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])${port("80")})$`;
93
82
 
94
83
  /** Where an instance is published: the base URL everything it serves hangs
95
84
  * under, ending in a slash and naming no route of its own.
@@ -120,54 +109,47 @@ const WEBUI_URL =
120
109
  export const Endpoint = Type.String({ $id: "Endpoint", pattern: BASE_URL });
121
110
  export type Endpoint = Static<typeof Endpoint>;
122
111
 
123
- /** Where the web UI is published: the base URL a person opens it at, ending in
124
- * a slash and naming no route of its own (`https://ui.example/ccmsg/`).
125
- *
126
- * The counterpart of `Endpoint` on the other side of the wire. An endpoint says
127
- * where an instance is dialed; this says where the page doing the dialing came
128
- * from, and one of each is what a credential is made against. Spelled to the
129
- * same rule as an endpoint, path and trailing slash included, because it is the
130
- * same kind of value: a base URL that something is published under — but held
131
- * to a narrower set of them: `https`, or `http` on a loopback name a browser
132
- * treats as trustworthy, and never an address literal for a host. Those are the
133
- * authenticator's conditions, not this contract's taste: a ceremony wants a
134
- * secure context, and a relying party is a domain. A URL outside them is one no
135
- * credential could have been made at.
136
- *
137
- * **What is kept and what is compared are different sizes.** The whole URL is
138
- * kept: it is where a person is sent, what an operator configures, and what
139
- * they read back in a list of their own credentials. Every comparison this
140
- * contract makes is of the origin (`originOf`) or the host (`rpIdOf`), because
141
- * a browser writes neither a path in an `Origin` header nor one in a
142
- * `clientDataJSON` — there is nothing finer on the wire to compare. Two web UIs
143
- * under one origin are therefore one place to everything here. The origin is
144
- * read off the URL where a header has to be matched rather than kept beside it
145
- * as a second field that could disagree. */
146
- export const WebUi = Type.String({ $id: "WebUi", pattern: WEBUI_URL });
147
- export type WebUi = Static<typeof WebUi>;
148
-
149
112
  /** Where a page was served from: a scheme and an authority and nothing else,
150
113
  * spelled as a browser spells it in the `Origin` header and in a credential's
151
114
  * `clientDataJSON` — no path, no trailing slash.
152
115
  *
116
+ * The one unit this contract holds a page to. A credential names one of these
117
+ * and a token family carries it over; nothing finer exists to name, a browser
118
+ * writing a path into neither header nor `clientDataJSON`. Serving two
119
+ * instances under one origin at different paths is therefore not a shape this
120
+ * contract has: the two would be one place to every check made here. Where that
121
+ * separation is wanted, the hosts are what a browser tells apart.
122
+ *
153
123
  * Apart from `Endpoint` because the two are units of different size and answer
154
- * different questions. An endpoint says which instance a person is admitted to
155
- * and is compared with its path; an origin says which site the page in front of
156
- * them came from, which is all the browser's same-origin rules know about and
157
- * all a page's own script cannot lie about. One site may be the page for many
158
- * endpoints, and one origin may carry many instances, so neither is derivable
159
- * from the other.
124
+ * different questions. An endpoint says where an instance is dialed; an origin
125
+ * says which site the page in front of a person came from, which is all the
126
+ * browser's same-origin rules know about and all a page's own script cannot lie
127
+ * about. One site may be the page for many endpoints, and one origin may carry
128
+ * many instances, so neither is derivable from the other and which instance a
129
+ * person may enter is answered by ownership rather than by either of them.
160
130
  *
161
131
  * Held to the one spelling a browser serializes: a lowercase scheme, a
162
132
  * lowercase host, and a port only where it is not the scheme's own. No
163
133
  * userinfo, no path, no trailing slash, nothing else a URL may carry.
164
134
  *
135
+ * Held to somewhere a WebAuthn ceremony could actually be held, too, which is
136
+ * narrower than what a URL parser takes and is the authenticator's rule rather
137
+ * than this contract's taste. A ceremony needs a secure context, so the scheme
138
+ * is `https` — with `http` on the loopback names browsers treat as trustworthy,
139
+ * which is what makes a page runnable on a development machine. And a relying
140
+ * party is a domain, so the host may not be an address literal: `https://198.51.100.9`
141
+ * is a perfectly good origin that could never hold a passkey. Writing it into
142
+ * the type rather than leaving it to the daemon is what keeps the relying party
143
+ * total over the values a record may carry — an origin no ceremony could run at
144
+ * would be a credential that could never have been made, accepted and
145
+ * replicated before anything noticed.
146
+ *
165
147
  * The narrowness is the point rather than pedantry. Every use of this value is
166
148
  * a whole-string comparison — against an `Origin` header, against a
167
149
  * `clientDataJSON.origin`, against the members of a CORS answer — so a second
168
150
  * spelling of one site would be a record that never matches the site it names,
169
151
  * or an allowed origin that quietly admits nothing. */
170
- export const Origin = Type.String({ $id: "Origin", pattern: authority("$") });
152
+ export const Origin = Type.String({ $id: "Origin", pattern: CEREMONY_ORIGIN });
171
153
  export type Origin = Static<typeof Origin>;
172
154
 
173
155
  /** A delivery-frame id: `<instance id>/<counter>`, numbered by the instance
package/src/schemas.ts CHANGED
@@ -15,8 +15,14 @@ import {
15
15
  AuthRegisterResponse,
16
16
  AuthResolveRequest,
17
17
  AuthResolveResponse,
18
- AuthRotateRequest,
19
- AuthRotateResponse,
18
+ AuthAccountReadRequest,
19
+ AuthAccountReadResponse,
20
+ AuthCredentialRemoveRequest,
21
+ AuthCredentialRemoveResponse,
22
+ AuthOwnershipRemoveRequest,
23
+ AuthOwnershipRemoveResponse,
24
+ AuthEnrollRequest,
25
+ AuthEnrollResponse,
20
26
  } from "./common/auth.ts";
21
27
  import {
22
28
  HelloInstanceRequest,
@@ -151,10 +157,19 @@ export const OP_SCHEMAS: Record<OpName, OpSchemas> = {
151
157
  "auth.challenge": { request: AuthChallengeRequest, response: AuthChallengeResponse },
152
158
  "auth.register": { request: AuthRegisterRequest, response: AuthRegisterResponse },
153
159
  "auth.assert": { request: AuthAssertRequest, response: AuthAssertResponse },
160
+ "auth.enroll": { request: AuthEnrollRequest, response: AuthEnrollResponse },
154
161
  "auth.token.refresh": { request: AuthTokenRefreshRequest, response: AuthTokenRefreshResponse },
155
162
  "auth.extend": { request: AuthExtendRequest, response: AuthExtendResponse },
163
+ "auth.account.read": { request: AuthAccountReadRequest, response: AuthAccountReadResponse },
164
+ "auth.ownership.remove": {
165
+ request: AuthOwnershipRemoveRequest,
166
+ response: AuthOwnershipRemoveResponse,
167
+ },
168
+ "auth.credential.remove": {
169
+ request: AuthCredentialRemoveRequest,
170
+ response: AuthCredentialRemoveResponse,
171
+ },
156
172
  "auth.resolve": { request: AuthResolveRequest, response: AuthResolveResponse },
157
- "auth.rotate": { request: AuthRotateRequest, response: AuthRotateResponse },
158
173
  "message.send": { request: MessageSendRequest, response: MessageSendResponse },
159
174
  "say.post": { request: SayPostRequest, response: SayPostResponse },
160
175
  "say.unread.clear": { request: SayUnreadClearRequest, response: SayUnreadClearResponse },