-
Notifications
You must be signed in to change notification settings - Fork 44
chore: update serializable type validation to check for wider range of cbor types instead of json types #1024
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,21 +1,17 @@ | ||||||
| import { betterAuth } from "better-auth"; | ||||||
| import { sqliteAdapter } from "@better-auth/sqlite"; | ||||||
| import Database from "better-sqlite3"; | ||||||
|
|
||||||
| const db = new Database("./auth.db"); | ||||||
|
|
||||||
| export const auth = betterAuth({ | ||||||
| // IMPORTANT: Connect your own database here | ||||||
| database: sqliteAdapter(db), | ||||||
| // IMPORTANT: Connect a real database for productoin use cases | ||||||
| // | ||||||
| // https://www.better-auth.com/docs/installation#create-database-tables | ||||||
| // database: memoryAdapter({ | ||||||
| // user: [], | ||||||
| // account: [], | ||||||
| // session: [], | ||||||
| // verifcation: [], | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a typo in the property name:
Suggested change
Spotted by Diamond |
||||||
| // }), | ||||||
| trustedOrigins: ["http://localhost:5173"], | ||||||
| emailAndPassword: { | ||||||
| enabled: true, | ||||||
| }, | ||||||
| session: { | ||||||
| expiresIn: 60 * 60 * 24 * 7, // 7 days | ||||||
| updateAge: 60 * 60 * 24, // 1 day (every day the session expiry is updated) | ||||||
| }, | ||||||
| plugins: [], | ||||||
| }); | ||||||
|
|
||||||
| export type Session = typeof auth.$Infer.Session; | ||||||
| export type User = typeof auth.$Infer.User; | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,31 +1,43 @@ | ||||||
| import { actor, setup } from "@rivetkit/actor"; | ||||||
| import { auth, type Session, type User } from "./auth"; | ||||||
| import { actor, OnAuthOptions, setup, UserError } from "@rivetkit/actor"; | ||||||
| import { auth } from "./auth"; | ||||||
|
|
||||||
| interface State { | ||||||
| messages: Message[]; | ||||||
| } | ||||||
|
|
||||||
| interface Message { | ||||||
| id: string; | ||||||
| userId: string; | ||||||
| username: string; | ||||||
| message: string; | ||||||
| timestamp: number; | ||||||
| } | ||||||
|
|
||||||
| export const chatRoom = actor({ | ||||||
| onAuth: async (c) => { | ||||||
| onAuth: async (c: OnAuthOptions) => { | ||||||
| const authResult = await auth.api.getSession({ | ||||||
| headers: c.req.headers, | ||||||
| }); | ||||||
| console.log("auth result", authResult); | ||||||
|
|
||||||
| if (!authResult?.session || !authResult?.user) { | ||||||
| throw new Error("Unauthorized"); | ||||||
| throw new UserError("Unauthorized"); | ||||||
| } | ||||||
|
|
||||||
| return { | ||||||
| userId: authResult.user.id, | ||||||
| user: authResult.user, | ||||||
| session: authResult.session, | ||||||
| }; | ||||||
| }, | ||||||
| state: { | ||||||
| messages: [] as Array<{ id: string; userId: string; username: string; message: string; timestamp: number }> | ||||||
| }, | ||||||
| state: { | ||||||
| messages: [], | ||||||
| } as State, | ||||||
| actions: { | ||||||
| sendMessage: (c, message: string) => { | ||||||
| const newMessage = { | ||||||
| id: crypto.randomUUID(), | ||||||
| userId: c.auth.userId, | ||||||
| username: c.auth.user.email, | ||||||
| userId: "TODO", | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
Spotted by Diamond |
||||||
| username: c.conn.auth.user.email || "Unknown", | ||||||
| message, | ||||||
| timestamp: Date.now(), | ||||||
| }; | ||||||
|
|
@@ -44,4 +56,3 @@ export const chatRoom = actor({ | |||||
| export const registry = setup({ | ||||||
| use: { chatRoom }, | ||||||
| }); | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,54 +1,27 @@ | ||
| import { registry } from "./registry"; | ||
| import { auth } from "./auth"; | ||
| import { Hono } from "hono"; | ||
| import { serve } from "@hono/node-server"; | ||
| import { cors } from "hono/cors"; | ||
|
|
||
| // Start RivetKit | ||
| const { client, hono, serve } = registry.createServer(); | ||
|
|
||
| // Setup router | ||
| const app = new Hono(); | ||
|
|
||
| // Start RivetKit | ||
| const { client, hono } = registry.run({ | ||
| driver: createMemoryDriver(), | ||
| cors: { | ||
| // IMPORTANT: Configure origins in production | ||
| origin: "*", | ||
| }, | ||
| }); | ||
| app.use( | ||
| "*", | ||
| cors({ | ||
| origin: ["http://localhost:5173"], | ||
| allowHeaders: ["Content-Type", "Authorization"], | ||
| allowMethods: ["POST", "GET", "OPTIONS"], | ||
| exposeHeaders: ["Content-Length"], | ||
| maxAge: 600, | ||
| credentials: true, | ||
| }), | ||
| ); | ||
|
|
||
| // Mount Better Auth routes | ||
| app.on(["GET", "POST"], "/api/auth/**", (c) => auth.handler(c.req.raw)); | ||
|
|
||
| // Expose RivetKit to the frontend | ||
| app.route("/registry", hono); | ||
|
|
||
| // Example HTTP endpoint to join chat room | ||
| app.post("/api/join-room/:roomId", async (c) => { | ||
| const roomId = c.req.param("roomId"); | ||
|
|
||
| // Verify authentication | ||
| const authResult = await auth.api.getSession({ | ||
| headers: c.req.header(), | ||
| }); | ||
|
|
||
| if (!authResult?.session || !authResult?.user) { | ||
| return c.json({ error: "Unauthorized" }, 401); | ||
| } | ||
|
|
||
| try { | ||
| const room = client.chatRoom.getOrCreate(roomId); | ||
| const messages = await room.getMessages(); | ||
|
|
||
| return c.json({ | ||
| success: true, | ||
| roomId, | ||
| messages, | ||
| user: authResult.user | ||
| }); | ||
| } catch (error) { | ||
| return c.json({ error: "Failed to join room" }, 500); | ||
| } | ||
| }); | ||
|
|
||
| serve({ fetch: app.fetch, port: 8080 }, () => | ||
| console.log("Listening at http://localhost:8080"), | ||
| ); | ||
| serve(app); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -37,10 +37,13 @@ export function safeStringify(obj: unknown, maxSize: number) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // it. Roll back state if fails to serialize. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Check if a value is JSON serializable. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Check if a value is CBOR serializable. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Optionally pass an onInvalid callback to receive the path to invalid values. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * For a complete list of supported CBOR tags, see: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * https://github.com/kriszyp/cbor-x/blob/cc1cf9df8ba72288c7842af1dd374d73e34cdbc1/README.md#list-of-supported-tags-for-decoding | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function isJsonSerializable( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function isCborSerializable( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: unknown, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onInvalid?: (path: string) => void, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentPath = "", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -62,30 +65,98 @@ export function isJsonSerializable( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle BigInt (CBOR tags 2 and 3) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof value === "bigint") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle Date objects (CBOR tags 0 and 1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (value instanceof Date) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle typed arrays (CBOR tags 64-82) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof Uint8Array || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof Uint8ClampedArray || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof Uint16Array || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof Uint32Array || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof BigUint64Array || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof Int8Array || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof Int16Array || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof Int32Array || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof BigInt64Array || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof Float32Array || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value instanceof Float64Array | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle Map (CBOR tag 259) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (value instanceof Map) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const [key, val] of value.entries()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const keyPath = currentPath ? `${currentPath}.key(${String(key)})` : `key(${String(key)})`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const valPath = currentPath ? `${currentPath}.value(${String(key)})` : `value(${String(key)})`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isCborSerializable(key, onInvalid, keyPath) || !isCborSerializable(val, onInvalid, valPath)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle Set (CBOR tag 258) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (value instanceof Set) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let index = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const item of value.values()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const itemPath = currentPath ? `${currentPath}.set[${index}]` : `set[${index}]`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isCborSerializable(item, onInvalid, itemPath)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| index++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle RegExp (CBOR tag 27) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (value instanceof RegExp) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle Error objects (CBOR tag 27) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (value instanceof Error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle arrays | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Array.isArray(value)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = 0; i < value.length; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const itemPath = currentPath ? `${currentPath}[${i}]` : `[${i}]`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isJsonSerializable(value[i], onInvalid, itemPath)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isCborSerializable(value[i], onInvalid, itemPath)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle plain objects | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle plain objects and records (CBOR tags 105, 51, 57344-57599) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof value === "object") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Reject if it's not a plain object | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Object.getPrototypeOf(value) !== Object.prototype) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onInvalid?.(currentPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Allow plain objects and objects with prototypes (for records and named objects) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const proto = Object.getPrototypeOf(value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (proto !== null && proto !== Object.prototype) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if it's a known serializable object type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const constructor = value.constructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (constructor && typeof constructor.name === "string") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Allow objects with named constructors (records, named objects) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This includes user-defined classes and built-in objects | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // that CBOR can serialize with tag 27 or record tags | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+145
to
+152
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The validation logic for objects with non-standard prototypes appears incomplete. While the code checks for the presence of a named constructor, it doesn't perform any actual validation to determine if these objects are CBOR serializable. The empty This could potentially allow non-serializable objects to pass validation, leading to runtime errors when serialization is attempted. Consider either:
// Example of a more complete implementation:
if (constructor && typeof constructor.name === "string") {
// Check if it's one of the known serializable types
if ([KnownType1, KnownType2].includes(constructor)) {
return true;
}
onInvalid?.(currentPath);
return false;
}
Suggested change
Spotted by Diamond |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check all properties recursively | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const key in value) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const propPath = currentPath ? `${currentPath}.${key}` : key; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| !isJsonSerializable( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| !isCborSerializable( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value[key as keyof typeof value], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onInvalid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| propPath, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a typo in the comment:
productoinshould beproductionSpotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.