-
Notifications
You must be signed in to change notification settings - Fork 120
chore: update serializable type validation to check for wider range of cbor types instead of json types #1024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.