@hollware/server 0.3.0 → 0.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.
- package/dist/{ceremonies-D7tsWU3S.mjs → ceremonies-V_1BbmZQ.mjs} +9 -16
- package/dist/index.d.mts +11 -0
- package/dist/index.mjs +297 -134
- package/dist/{store-Sq0pwUQv.mjs → store-CsuER6J7.mjs} +44 -54
- package/migrations/0006_people_across_sites.sql +54 -0
- package/migrations/0007_read_tokens.sql +4 -0
- package/migrations/meta/0006_snapshot.json +2102 -0
- package/migrations/meta/0007_snapshot.json +2110 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as findInvitation, c as invitationById, d as
|
|
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-CsuER6J7.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";
|
|
@@ -17,10 +17,10 @@ function refused(message, fix) {
|
|
|
17
17
|
}
|
|
18
18
|
/** The options of `navigator.credentials.create` for the person of an invitation. */
|
|
19
19
|
async function registrationOptions(ceremony, invitationSecret) {
|
|
20
|
-
const invitation = await findInvitation(ceremony.db,
|
|
20
|
+
const invitation = await findInvitation(ceremony.db, invitationSecret, ceremony.now);
|
|
21
21
|
if (invitation === null) throw refused("This link is not valid: it was used already, or it expired.", "Ask the owner of this Hollware for a new invitation.");
|
|
22
22
|
const userId = invitation.userId ?? newId("usr");
|
|
23
|
-
const existing = invitation.userId === null ? [] : await passkeysOf(ceremony.db,
|
|
23
|
+
const existing = invitation.userId === null ? [] : await passkeysOf(ceremony.db, invitation.userId);
|
|
24
24
|
const rp = relyingParty(ceremony);
|
|
25
25
|
const options = await generateRegistrationOptions({
|
|
26
26
|
rpName: "Hollware",
|
|
@@ -36,7 +36,6 @@ async function registrationOptions(ceremony, invitationSecret) {
|
|
|
36
36
|
});
|
|
37
37
|
return {
|
|
38
38
|
challengeId: await saveChallenge(ceremony.db, {
|
|
39
|
-
site: ceremony.site,
|
|
40
39
|
challenge: options.challenge,
|
|
41
40
|
purpose: "register",
|
|
42
41
|
invitationId: invitation.id,
|
|
@@ -52,7 +51,6 @@ async function registrationOptions(ceremony, invitationSecret) {
|
|
|
52
51
|
*/
|
|
53
52
|
async function verifyRegistration(ceremony, input) {
|
|
54
53
|
const challenge = await takeChallenge(ceremony.db, {
|
|
55
|
-
site: ceremony.site,
|
|
56
54
|
id: input.challengeId,
|
|
57
55
|
purpose: "register",
|
|
58
56
|
now: ceremony.now
|
|
@@ -66,18 +64,17 @@ async function verifyRegistration(ceremony, input) {
|
|
|
66
64
|
expectedRPID: rp.id
|
|
67
65
|
});
|
|
68
66
|
if (!verified.verified) throw refused("The passkey could not be verified.", "Try again.");
|
|
69
|
-
const invitation = await invitationById(ceremony.db,
|
|
70
|
-
if (invitation === null || !await redeemInvitation(ceremony.db,
|
|
67
|
+
const invitation = await invitationById(ceremony.db, challenge.invitationId);
|
|
68
|
+
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.");
|
|
71
69
|
if (invitation.userId === null) await createUser(ceremony.db, {
|
|
72
70
|
id: challenge.userId,
|
|
73
71
|
name: input.name.trim() === "" ? "Unnamed" : input.name.trim(),
|
|
74
72
|
role: invitation.role,
|
|
75
|
-
site:
|
|
73
|
+
site: invitation.site,
|
|
76
74
|
now: ceremony.now
|
|
77
75
|
});
|
|
78
76
|
const { credential } = verified.registrationInfo;
|
|
79
77
|
await addPasskey(ceremony.db, {
|
|
80
|
-
site: ceremony.site,
|
|
81
78
|
userId: challenge.userId,
|
|
82
79
|
credentialId: credential.id,
|
|
83
80
|
publicKey: isoBase64URL.fromBuffer(credential.publicKey),
|
|
@@ -87,7 +84,6 @@ async function verifyRegistration(ceremony, input) {
|
|
|
87
84
|
now: ceremony.now
|
|
88
85
|
});
|
|
89
86
|
const session = await createSession(ceremony.db, {
|
|
90
|
-
site: ceremony.site,
|
|
91
87
|
userId: challenge.userId,
|
|
92
88
|
now: ceremony.now,
|
|
93
89
|
hours: SESSION_HOURS
|
|
@@ -97,7 +93,7 @@ async function verifyRegistration(ceremony, input) {
|
|
|
97
93
|
session: session.secret
|
|
98
94
|
};
|
|
99
95
|
}
|
|
100
|
-
/** The options of `navigator.credentials.get`: any passkey of this
|
|
96
|
+
/** The options of `navigator.credentials.get`: any passkey of this deployment, chosen by the person. */
|
|
101
97
|
async function authenticationOptions(ceremony) {
|
|
102
98
|
const rp = relyingParty(ceremony);
|
|
103
99
|
const options = await generateAuthenticationOptions({
|
|
@@ -106,7 +102,6 @@ async function authenticationOptions(ceremony) {
|
|
|
106
102
|
});
|
|
107
103
|
return {
|
|
108
104
|
challengeId: await saveChallenge(ceremony.db, {
|
|
109
|
-
site: ceremony.site,
|
|
110
105
|
challenge: options.challenge,
|
|
111
106
|
purpose: "authenticate",
|
|
112
107
|
invitationId: null,
|
|
@@ -116,16 +111,15 @@ async function authenticationOptions(ceremony) {
|
|
|
116
111
|
options
|
|
117
112
|
};
|
|
118
113
|
}
|
|
119
|
-
/** Verifies a sign-in with a passkey of this
|
|
114
|
+
/** Verifies a sign-in with a passkey of this deployment, and opens the person's session. */
|
|
120
115
|
async function verifyAuthentication(ceremony, input) {
|
|
121
116
|
const challenge = await takeChallenge(ceremony.db, {
|
|
122
|
-
site: ceremony.site,
|
|
123
117
|
id: input.challengeId,
|
|
124
118
|
purpose: "authenticate",
|
|
125
119
|
now: ceremony.now
|
|
126
120
|
});
|
|
127
121
|
if (challenge === null) throw refused("The sign-in expired.", "Sign in again.");
|
|
128
|
-
const passkey = await findPasskey(ceremony.db,
|
|
122
|
+
const passkey = await findPasskey(ceremony.db, input.response.id);
|
|
129
123
|
if (passkey === null) throw refused("This passkey is not one of this Hollware.", "Use a passkey you registered here.");
|
|
130
124
|
const rp = relyingParty(ceremony);
|
|
131
125
|
const verified = await verifyAuthenticationResponse({
|
|
@@ -143,7 +137,6 @@ async function verifyAuthentication(ceremony, input) {
|
|
|
143
137
|
if (!verified.verified) throw refused("The passkey could not be verified.", "Try again.");
|
|
144
138
|
await touchPasskey(ceremony.db, passkey.id, verified.authenticationInfo.newCounter, ceremony.now);
|
|
145
139
|
const session = await createSession(ceremony.db, {
|
|
146
|
-
site: ceremony.site,
|
|
147
140
|
userId: passkey.userId,
|
|
148
141
|
now: ceremony.now,
|
|
149
142
|
hours: SESSION_HOURS
|
package/dist/index.d.mts
CHANGED
|
@@ -333,6 +333,17 @@ type Principal = {
|
|
|
333
333
|
readonly site: string;
|
|
334
334
|
/** The scopes of the grant intersected with those of the user's role, as of now. */
|
|
335
335
|
readonly scopes: readonly Scope[];
|
|
336
|
+
} | {
|
|
337
|
+
/**
|
|
338
|
+
* A read token (`hw_read_`): it reads the published content of its site over the read API,
|
|
339
|
+
* and the gateway turns it away from every other route.
|
|
340
|
+
*/
|
|
341
|
+
readonly kind: 'read';
|
|
342
|
+
readonly userId: string;
|
|
343
|
+
readonly tokenId: string;
|
|
344
|
+
readonly site: string;
|
|
345
|
+
/** `content:read` while its user's role has it, else nothing. */
|
|
346
|
+
readonly scopes: readonly Scope[];
|
|
336
347
|
} | {
|
|
337
348
|
/** A Worker of the same account, over the RPC binding: it reads published content. */
|
|
338
349
|
readonly kind: 'binding';
|