@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +15 -7
  3. package/bin/voidbase.ts +58 -6
  4. package/docs/adapter.md +271 -0
  5. package/docs/ci.md +195 -0
  6. package/docs/deploy.md +60 -5
  7. package/docs/releasing.md +39 -31
  8. package/hooks-plugin.ts +15 -4
  9. package/package.json +9 -4
  10. package/routes/api/[...path].ts +0 -5
  11. package/scripts/cf-builds.ts +228 -0
  12. package/scripts/ci-browser.sh +60 -0
  13. package/scripts/ci-cache.sh +40 -0
  14. package/scripts/ci-lib.sh +40 -0
  15. package/scripts/ci-oracles.sh +19 -0
  16. package/scripts/ci-plan.ts +270 -0
  17. package/scripts/ci-status.ts +126 -0
  18. package/scripts/ci-suites.sh +11 -1
  19. package/scripts/ci.sh +188 -0
  20. package/scripts/gh-release.ts +48 -0
  21. package/scripts/release.sh +104 -0
  22. package/scripts/seed-reference.sh +7 -2
  23. package/scripts/sync-app.ts +1 -0
  24. package/src/adapter/bundle.ts +130 -0
  25. package/src/adapter/codegen.ts +269 -0
  26. package/src/adapter/index.ts +6 -0
  27. package/src/adapter/plugin.ts +117 -0
  28. package/src/adapter/runtime.ts +325 -0
  29. package/src/adapter/scan.ts +276 -0
  30. package/src/cloud/rest.ts +10 -2
  31. package/src/node/assets.ts +7 -1
  32. package/src/node/cloud-init.ts +14 -0
  33. package/src/node/deploy-cf.ts +113 -16
  34. package/src/node/secrets.ts +169 -0
  35. package/src/node/serve.ts +15 -2
  36. package/src/server/api.ts +7 -2
  37. package/src/server/app.ts +6 -1
  38. package/src/server/hooks/index.ts +27 -1
  39. package/src/server/hooks/migrations.ts +4 -1
  40. package/src/server/hooks/runtime.ts +10 -2
  41. package/src/server/jobs.ts +3 -1
  42. package/src/server/webauthn.ts +23 -6
  43. package/tsconfig.json +4 -0
  44. package/tsconfig.node.json +2 -1
@@ -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
- import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse } from "@simplewebauthn/server";
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
- export function mountWebAuthn(app: 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 }) {
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
@@ -28,6 +28,10 @@
28
28
  "bin",
29
29
  "hooks-plugin.ts",
30
30
  "scripts",
31
+ "src/adapter/codegen.ts",
32
+ "src/adapter/index.ts",
33
+ "src/adapter/plugin.ts",
34
+ "src/adapter/scan.ts",
31
35
  "src/node",
32
36
  "src/platform/node",
33
37
  "surface",
@@ -22,6 +22,7 @@
22
22
  "src/platform/node",
23
23
  "src/server",
24
24
  "hooks-plugin.ts",
25
- "bin"
25
+ "bin",
26
+ "src/adapter"
26
27
  ]
27
28
  }