@voidbase-cloud/voidbase 0.2.2 → 0.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/CHANGELOG.md +47 -0
- package/README.md +15 -7
- package/bin/voidbase.ts +58 -6
- package/docs/adapter.md +271 -0
- package/docs/ci.md +195 -0
- package/docs/deploy.md +60 -5
- package/docs/releasing.md +39 -31
- package/hooks-plugin.ts +15 -4
- package/package.json +9 -4
- package/routes/api/[...path].ts +0 -5
- package/scripts/cf-builds.ts +228 -0
- package/scripts/ci-browser.sh +60 -0
- package/scripts/ci-cache.sh +40 -0
- package/scripts/ci-lib.sh +40 -0
- package/scripts/ci-oracles.sh +19 -0
- package/scripts/ci-plan.ts +270 -0
- package/scripts/ci-status.ts +126 -0
- package/scripts/ci-suites.sh +11 -1
- package/scripts/ci.sh +188 -0
- package/scripts/gh-release.ts +48 -0
- package/scripts/release.sh +104 -0
- package/scripts/seed-reference.sh +7 -2
- package/scripts/sync-app.ts +1 -0
- package/src/adapter/bundle.ts +130 -0
- package/src/adapter/codegen.ts +269 -0
- package/src/adapter/index.ts +6 -0
- package/src/adapter/plugin.ts +117 -0
- package/src/adapter/runtime.ts +325 -0
- package/src/adapter/scan.ts +276 -0
- package/src/cloud/rest.ts +10 -2
- package/src/node/assets.ts +7 -1
- package/src/node/cloud-init.ts +14 -0
- package/src/node/deploy-cf.ts +113 -16
- package/src/node/secrets.ts +169 -0
- package/src/node/serve.ts +15 -2
- package/src/server/api.ts +7 -2
- package/src/server/app.ts +6 -1
- package/src/server/hooks/index.ts +27 -1
- package/src/server/hooks/migrations.ts +4 -1
- package/src/server/hooks/runtime.ts +10 -2
- package/src/server/jobs.ts +3 -1
- package/src/server/webauthn.ts +23 -6
- package/tsconfig.json +4 -0
- package/tsconfig.node.json +2 -1
package/src/server/webauthn.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// Credentials live in the app's `passkeys` collection (user, credential_id, credentials) exactly like the Go code;
|
|
4
4
|
// the pending challenge lives in _params (the Go version keeps it in memory, which a Worker cannot rely on).
|
|
5
5
|
import type { Context, Hono } from "hono";
|
|
6
|
-
|
|
6
|
+
// loaded on the first passkey request rather than at startup: @simplewebauthn/server is half a megabyte, and most
|
|
7
|
+
// apps have no `passkeys` collection and never reach these routes
|
|
8
|
+
const webauthn = () => import("@simplewebauthn/server");
|
|
7
9
|
import { recordAuthResponse } from "./auth-response";
|
|
8
10
|
import { loadCollections } from "./collections/model";
|
|
9
11
|
import { all, ident, one, run } from "./db";
|
|
@@ -78,14 +80,29 @@ async function takeSession(db: D1Database, userId: string): Promise<string | nul
|
|
|
78
80
|
return s.expires > Date.now() ? s.challenge : null;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
type Router = Pick<Hono<AppEnv>, "get" | "post"> | { get: (p: string, h: (c: Context<AppEnv>) => Promise<Response>) => void; post: (p: string, h: (c: Context<AppEnv>) => Promise<Response>) => void };
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Mounts the four passkey endpoints. voidbase's own app mounts them, so an app gets them for free; the export is
|
|
87
|
+
* for anyone putting them on a router of their own.
|
|
88
|
+
*
|
|
89
|
+
* They answer only when the app has a `passkeys` collection. Without one there is nowhere to keep a credential, so
|
|
90
|
+
* the feature is off and the endpoints are not there.
|
|
91
|
+
*/
|
|
92
|
+
export function mountWebAuthn(router: Router) {
|
|
93
|
+
const gated = (h: (c: Context<AppEnv>) => Promise<Response>) => async (c: Context<AppEnv>) => {
|
|
94
|
+
if (!(await loadCollections(c.env.DB)).has("passkeys")) return c.json({ status: 404, message: "Not Found.", data: {} }, 404);
|
|
95
|
+
return h(c);
|
|
96
|
+
};
|
|
97
|
+
const app = { get: (p: string, h: (c: Context<AppEnv>) => Promise<Response>) => router.get(p, gated(h) as never), post: (p: string, h: (c: Context<AppEnv>) => Promise<Response>) => router.post(p, gated(h) as never) };
|
|
98
|
+
|
|
82
99
|
app.get("/api/webauthn/registration-options", async (c) => {
|
|
83
100
|
const user = await findUser(c.env.DB, c.req.query("usernameOrEmail") ?? "");
|
|
84
101
|
if (!user) return c.json(RESPONSES.failed, 400);
|
|
85
102
|
try {
|
|
86
103
|
const rp = await relyingParty(c);
|
|
87
104
|
const existing = await credentialsOf(c.env.DB, String(user.id));
|
|
88
|
-
const options = await generateRegistrationOptions({
|
|
105
|
+
const options = await (await webauthn()).generateRegistrationOptions({
|
|
89
106
|
rpName: rp.rpName, rpID: rp.rpID,
|
|
90
107
|
userID: new TextEncoder().encode(String(user.id)),
|
|
91
108
|
userName: String(user.username || user.email || user.id),
|
|
@@ -110,7 +127,7 @@ export function mountWebAuthn(app: Pick<Hono<AppEnv>, "get" | "post"> | { get: (
|
|
|
110
127
|
const challenge = await takeSession(c.env.DB, String(user.id));
|
|
111
128
|
if (!challenge) return c.json(RESPONSES.reg_error, 500);
|
|
112
129
|
const { usernameOrEmail: _u, ...response } = body;
|
|
113
|
-
const verification = await verifyRegistrationResponse({ response: response as never, expectedChallenge: challenge, expectedOrigin: rp.origin, expectedRPID: rp.rpID, requireUserVerification: false });
|
|
130
|
+
const verification = await (await webauthn()).verifyRegistrationResponse({ response: response as never, expectedChallenge: challenge, expectedOrigin: rp.origin, expectedRPID: rp.rpID, requireUserVerification: false });
|
|
114
131
|
if (!verification.verified || !verification.registrationInfo) return c.json(RESPONSES.reg_error, 500);
|
|
115
132
|
const info = verification.registrationInfo;
|
|
116
133
|
const cred: StoredCredential = {
|
|
@@ -131,7 +148,7 @@ export function mountWebAuthn(app: Pick<Hono<AppEnv>, "get" | "post"> | { get: (
|
|
|
131
148
|
try {
|
|
132
149
|
const rp = await relyingParty(c);
|
|
133
150
|
const creds = await credentialsOf(c.env.DB, String(user.id));
|
|
134
|
-
const options = await generateAuthenticationOptions({ rpID: rp.rpID, userVerification: "preferred", allowCredentials: creds.map((e) => ({ id: e.cred.id, transports: e.cred.transports })) });
|
|
151
|
+
const options = await (await webauthn()).generateAuthenticationOptions({ rpID: rp.rpID, userVerification: "preferred", allowCredentials: creds.map((e) => ({ id: e.cred.id, transports: e.cred.transports })) });
|
|
135
152
|
await putSession(c.env.DB, String(user.id), options.challenge);
|
|
136
153
|
return c.json({ publicKey: options });
|
|
137
154
|
} catch (err) {
|
|
@@ -151,7 +168,7 @@ export function mountWebAuthn(app: Pick<Hono<AppEnv>, "get" | "post"> | { get: (
|
|
|
151
168
|
const { usernameOrEmail: _u, ...response } = body;
|
|
152
169
|
const match = (await credentialsOf(c.env.DB, String(user.id))).find((e) => e.cred.id === response.id);
|
|
153
170
|
if (!match) return c.json(RESPONSES.login_error, 500);
|
|
154
|
-
const verification = await verifyAuthenticationResponse({
|
|
171
|
+
const verification = await (await webauthn()).verifyAuthenticationResponse({
|
|
155
172
|
response: response as never, expectedChallenge: challenge, expectedOrigin: rp.origin, expectedRPID: rp.rpID, requireUserVerification: false,
|
|
156
173
|
credential: { id: match.cred.id, publicKey: b64url.decode(match.cred.publicKey), counter: match.cred.counter, transports: match.cred.transports },
|
|
157
174
|
});
|
package/tsconfig.json
CHANGED