@mdbase-dev/connect 0.1.0-beta.23 → 0.1.0-beta.25
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/README.md +26 -8
- package/dist/application-identity.d.ts +5 -0
- package/dist/application-identity.js +56 -0
- package/dist/browser/integrity.json +1 -1
- package/dist/browser/mdbase-connect.min.js +3 -3
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
- package/dist/connect-options.d.ts +22 -0
- package/dist/connect-options.js +1 -0
- package/dist/connect.d.ts +40 -29
- package/dist/connect.js +183 -79
- package/dist/connection.d.ts +14 -2
- package/dist/first-contact-presentation.d.ts +5 -0
- package/dist/first-contact-presentation.js +51 -0
- package/dist/first-contact.d.ts +14 -0
- package/dist/first-contact.js +145 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/internal-types.d.ts +5 -1
- package/dist/mdbase-connect.d.ts +21 -0
- package/dist/mdbase-connect.js +42 -0
- package/dist/outcomes.d.ts +3 -3
- package/dist/outcomes.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,7 +33,14 @@ if (!started.ok) {
|
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
if (session.getSnapshot().status === "unselected") {
|
|
36
|
-
const authorized = await session.authorize("choose"
|
|
36
|
+
const authorized = await session.authorize("choose", {
|
|
37
|
+
onFirstContact: ({ authenticationString }) => {
|
|
38
|
+
showPersistentSecurityPrompt({
|
|
39
|
+
title: "Compare with mdbase connect",
|
|
40
|
+
code: authenticationString
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
37
44
|
if (!authorized.ok) {
|
|
38
45
|
renderProblem(authorized.problem);
|
|
39
46
|
return;
|
|
@@ -72,15 +79,15 @@ problem model, setup failures, mutation uncertainty, and UI guidance.
|
|
|
72
79
|
|
|
73
80
|
`MdbaseConnect` is the application-level authorization registry.
|
|
74
81
|
`MdbaseSession` is the normal application boundary: it owns active collection
|
|
75
|
-
selection, authorization
|
|
82
|
+
selection, authorization completion, access capabilities, and one
|
|
76
83
|
reactive snapshot. Use `getSnapshot()` and `subscribe()` directly or through
|
|
77
84
|
your framework's external-store integration. A snapshot is `unselected`,
|
|
78
85
|
`ready`, or `unavailable`; an unavailable bookmark includes the explicit
|
|
79
86
|
collection ID and reason instead of collapsing every recovery state to `null`.
|
|
80
87
|
|
|
81
88
|
`MdbaseBrowserSelection` keeps the stable collection ID in
|
|
82
|
-
`?collection=<id>`, preserves unrelated path/query/hash and router state,
|
|
83
|
-
|
|
89
|
+
`?collection=<id>`, preserves unrelated path/query/hash and router state, and
|
|
90
|
+
reports browser back/forward
|
|
84
91
|
changes to the session. The session auto-selects only when exactly one saved
|
|
85
92
|
connection exists. Switching is state-driven and must not reload the page:
|
|
86
93
|
|
|
@@ -104,6 +111,16 @@ Collection IDs are opaque non-secret locators that may appear in browser
|
|
|
104
111
|
history and logs. Grants remain the authorization boundary, and names are
|
|
105
112
|
display text that may change.
|
|
106
113
|
|
|
114
|
+
When the chosen collection is owned by a computer that has not seen this
|
|
115
|
+
application installation before, `onFirstContact` is required. The SDK derives
|
|
116
|
+
the eight-character value locally from the application's non-extractable
|
|
117
|
+
installation key and the connector key; it never accepts a value supplied by
|
|
118
|
+
the control plane. Keep the value visible while the user compares and accepts
|
|
119
|
+
the exact same value in mdbase connect. Headless operators use
|
|
120
|
+
`mdbase connect trust list`, `trust show <id>`, and
|
|
121
|
+
`trust accept <id> --code XXXX-XXXX`. Hosted-only authorization does not invoke
|
|
122
|
+
this callback.
|
|
123
|
+
|
|
107
124
|
Bundled v1 application manifests can declare runtime notification criteria.
|
|
108
125
|
Register a service worker from a user gesture to receive standards-based Web
|
|
109
126
|
Push while the app is closed:
|
|
@@ -378,12 +395,13 @@ Authorization is retained in `localStorage` by default. Access tokens are
|
|
|
378
395
|
renewed with rotating refresh tokens; passing a custom `Storage` implementation
|
|
379
396
|
allows a host to choose another persistence boundary.
|
|
380
397
|
|
|
381
|
-
Native shells can pass `navigate` to open the
|
|
398
|
+
Native shells can pass `navigate` to open the approval URL in the system
|
|
382
399
|
browser and list a reverse-domain callback such as
|
|
383
400
|
`dev.mdbase.worklog://auth/mdbase/callback` in the bundled declaration. Its
|
|
384
|
-
scheme must match the declaration ID.
|
|
385
|
-
|
|
386
|
-
|
|
401
|
+
scheme must match the declaration ID. The application remains alive, polls the
|
|
402
|
+
PKCE-bound status endpoint, displays first contact when required, and receives
|
|
403
|
+
the completed connection directly from `authorize()`; the browser does not
|
|
404
|
+
carry credentials back through a deep link.
|
|
387
405
|
|
|
388
406
|
New authorizations require encrypted relay protocol 1 by default. The SDK keeps
|
|
389
407
|
independent non-extractable P-256 ECDH agreement and ECDSA signing keys plus an
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { authorizationSigningMessage, type ApplicationAuthorizationBinding, type ApplicationAuthorizationProof } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import type { GrantKeyRecord } from "./crypto.js";
|
|
3
|
+
export { authorizationSigningMessage };
|
|
4
|
+
export declare function applicationInstallationId(identity: Pick<GrantKeyRecord, "agreementPublicKey" | "signingPublicKey">): Promise<string>;
|
|
5
|
+
export declare function signApplicationAuthorization(binding: ApplicationAuthorizationBinding, identity: Pick<GrantKeyRecord, "signingPrivateKey" | "agreementPublicKey" | "signingPublicKey">): Promise<ApplicationAuthorizationProof>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { applicationInstallationIdFromPublicKeys, authorizationSigningMessage } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
const P256_ORDER = BigInt("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551");
|
|
3
|
+
const P256_HALF_ORDER = P256_ORDER / 2n;
|
|
4
|
+
export { authorizationSigningMessage };
|
|
5
|
+
export async function applicationInstallationId(identity) {
|
|
6
|
+
return applicationInstallationIdFromPublicKeys(identity.agreementPublicKey, identity.signingPublicKey);
|
|
7
|
+
}
|
|
8
|
+
export async function signApplicationAuthorization(binding, identity) {
|
|
9
|
+
if (binding.installation_agreement_public_key !== identity.agreementPublicKey
|
|
10
|
+
|| binding.installation_signing_public_key !== identity.signingPublicKey
|
|
11
|
+
|| binding.application_installation_id !== await applicationInstallationId(identity)) {
|
|
12
|
+
throw new Error("Application authorization identity does not match its binding.");
|
|
13
|
+
}
|
|
14
|
+
const signature = canonicalSignature(new Uint8Array(await crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, identity.signingPrivateKey, authorizationSigningMessage(binding))));
|
|
15
|
+
return {
|
|
16
|
+
binding,
|
|
17
|
+
signature: base64Url(signature)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function concat(values) {
|
|
21
|
+
const output = new Uint8Array(values.reduce((length, value) => length + value.byteLength, 0));
|
|
22
|
+
let offset = 0;
|
|
23
|
+
for (const value of values) {
|
|
24
|
+
output.set(value, offset);
|
|
25
|
+
offset += value.byteLength;
|
|
26
|
+
}
|
|
27
|
+
return output;
|
|
28
|
+
}
|
|
29
|
+
function base64Url(value) {
|
|
30
|
+
return btoa(String.fromCharCode(...value))
|
|
31
|
+
.replaceAll("+", "-")
|
|
32
|
+
.replaceAll("/", "_")
|
|
33
|
+
.replace(/=+$/u, "");
|
|
34
|
+
}
|
|
35
|
+
function canonicalSignature(signature) {
|
|
36
|
+
if (signature.byteLength !== 64) {
|
|
37
|
+
throw new Error("Application authorization signature is invalid.");
|
|
38
|
+
}
|
|
39
|
+
const s = bytesBigInt(signature.slice(32));
|
|
40
|
+
if (s === 0n || s >= P256_ORDER) {
|
|
41
|
+
throw new Error("Application authorization signature is invalid.");
|
|
42
|
+
}
|
|
43
|
+
if (s <= P256_HALF_ORDER)
|
|
44
|
+
return signature;
|
|
45
|
+
return concat([signature.slice(0, 32), bigIntBytes(P256_ORDER - s, 32)]);
|
|
46
|
+
}
|
|
47
|
+
function bytesBigInt(value) {
|
|
48
|
+
return BigInt(`0x${hex(value)}`);
|
|
49
|
+
}
|
|
50
|
+
function bigIntBytes(value, length) {
|
|
51
|
+
const encoded = value.toString(16).padStart(length * 2, "0");
|
|
52
|
+
return Uint8Array.from({ length }, (_, index) => Number.parseInt(encoded.slice(index * 2, index * 2 + 2), 16));
|
|
53
|
+
}
|
|
54
|
+
function hex(value) {
|
|
55
|
+
return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
56
|
+
}
|