@ccmsg/protocol 2.2.0 → 2.3.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/package.json +1 -1
- package/src/attributes.ts +6 -4
- package/src/common/auth.ts +4 -2
- package/src/identifiers.ts +38 -9
package/package.json
CHANGED
package/src/attributes.ts
CHANGED
|
@@ -42,10 +42,12 @@ export interface OpAttributes {
|
|
|
42
42
|
* Being reachable from a page is also what gives the three that settle an
|
|
43
43
|
* identity the only headers this contract reads over HTTP: the `Origin` a
|
|
44
44
|
* browser states, held to the origin of the web UI the credential or the
|
|
45
|
-
* registration names, and `Sec-Fetch-Site`, which has to
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* registration names, and `Sec-Fetch-Site`, which has to be one of
|
|
46
|
+
* `same-origin`, `same-site` or `cross-site` — the three that say a page made
|
|
47
|
+
* the call. Anything else fails: `none`, which is a request with no initiator
|
|
48
|
+
* at all, a header that is absent, and a value this contract does not know.
|
|
49
|
+
* Both headers are read as an allowlist rather than a blocklist, and either
|
|
50
|
+
* failing answers `auth_invalid` without saying which. `auth.challenge` is checked
|
|
49
51
|
* against neither, having nothing yet to be checked against; what it hands
|
|
50
52
|
* out is spendable only at its issuer. */
|
|
51
53
|
readonly carrier?: "http";
|
package/src/common/auth.ts
CHANGED
|
@@ -200,8 +200,10 @@ export type AuthRegisterArgs = Static<typeof AuthRegisterArgs>;
|
|
|
200
200
|
* the cookie exists to have. That holds however far the page is from the
|
|
201
201
|
* endpoint: a cookie the page's own site cannot reach is sent from a site it
|
|
202
202
|
* does not own only as a partitioned one, which keeps a session taken at one
|
|
203
|
-
* site from being carried to another
|
|
204
|
-
*
|
|
203
|
+
* site from being carried to another. That partition is by site, where a
|
|
204
|
+
* credential is by origin, so it is the `Origin` held against this family's
|
|
205
|
+
* `webui` that keeps a session to the one place it was made — the cookie's
|
|
206
|
+
* partition answers for sites and nothing finer. Sending it at all across sites takes a browser that partitions
|
|
205
207
|
* cookies, which is a premise of this contract rather than a case it
|
|
206
208
|
* accommodates: one that does not is not an environment this is spoken over. */
|
|
207
209
|
export const AuthSession = Type.Object(
|
package/src/identifiers.ts
CHANGED
|
@@ -43,12 +43,14 @@ export const InstanceId = Type.String({
|
|
|
43
43
|
});
|
|
44
44
|
export type InstanceId = Static<typeof InstanceId>;
|
|
45
45
|
|
|
46
|
-
/** A host as a browser serializes one: lowercase labels,
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
/** A host name as a browser serializes one: lowercase labels, no uppercase, no
|
|
47
|
+
* userinfo and no empty label — each of those is either a second spelling of
|
|
48
|
+
* one host or a string no URL parser will take. */
|
|
49
|
+
const HOST_NAME = "[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*";
|
|
50
|
+
|
|
51
|
+
/** A host: a name as above, or an address literal in its brackets (no zone id,
|
|
52
|
+
* which no URL parser takes). */
|
|
53
|
+
const HOST = `(?:${HOST_NAME}|\\[[0-9a-f:.]+\\])`;
|
|
52
54
|
|
|
53
55
|
/** A port in range, 1 to 65535. */
|
|
54
56
|
const PORT =
|
|
@@ -58,8 +60,11 @@ const PORT =
|
|
|
58
60
|
* browser omits it, so writing it would be a second name for one place. `tail`
|
|
59
61
|
* closes the pattern: the end of the string for an origin, a path for a base
|
|
60
62
|
* URL, and it is what the lookaheads read to know a port ended. */
|
|
63
|
+
function port(its: string): string {
|
|
64
|
+
return `(?::(?!${its}(?:/|$))${PORT})?`;
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
function authority(tail: string): string {
|
|
62
|
-
const port = (its: string) => `(?::(?!${its}(?:/|$))${PORT})?`;
|
|
63
68
|
return `^(?:https://${HOST}${port("443")}|http://${HOST}${port("80")})${tail}`;
|
|
64
69
|
}
|
|
65
70
|
|
|
@@ -67,6 +72,25 @@ function authority(tail: string): string {
|
|
|
67
72
|
* carries no query or fragment. */
|
|
68
73
|
const BASE_URL = authority("(?:/[^?#\\s]*)?/$");
|
|
69
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}`;
|
|
93
|
+
|
|
70
94
|
/** Where an instance is published: the base URL everything it serves hangs
|
|
71
95
|
* under, ending in a slash and naming no route of its own.
|
|
72
96
|
*
|
|
@@ -103,7 +127,12 @@ export type Endpoint = Static<typeof Endpoint>;
|
|
|
103
127
|
* where an instance is dialed; this says where the page doing the dialing came
|
|
104
128
|
* from, and one of each is what a credential is made against. Spelled to the
|
|
105
129
|
* same rule as an endpoint, path and trailing slash included, because it is the
|
|
106
|
-
* same kind of value: a base URL that something is published under
|
|
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.
|
|
107
136
|
*
|
|
108
137
|
* **What is kept and what is compared are different sizes.** The whole URL is
|
|
109
138
|
* kept: it is where a person is sent, what an operator configures, and what
|
|
@@ -114,7 +143,7 @@ export type Endpoint = Static<typeof Endpoint>;
|
|
|
114
143
|
* under one origin are therefore one place to everything here. The origin is
|
|
115
144
|
* read off the URL where a header has to be matched rather than kept beside it
|
|
116
145
|
* as a second field that could disagree. */
|
|
117
|
-
export const WebUi = Type.String({ $id: "WebUi", pattern:
|
|
146
|
+
export const WebUi = Type.String({ $id: "WebUi", pattern: WEBUI_URL });
|
|
118
147
|
export type WebUi = Static<typeof WebUi>;
|
|
119
148
|
|
|
120
149
|
/** Where a page was served from: a scheme and an authority and nothing else,
|