Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 9 additions & 13 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
PORT=3000
GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxx
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google
SESSION_SECRET=your-super-secret-key-change-in-production

# Database Configuration
POSTGRES_HOST=localhost
POSTGRES_PORT=5437
POSTGRES_USER=postgres
POSTGRES_PASSWORD=mysecretpassword
POSTGRES_DB=postgres
# Wrangler dev reads secrets from .dev.vars (not .env)
# Copy this file to .dev.vars and fill in values

SESSION_SECRET=your-super-secret-key-change-in-production
GOOGLE_ID=xxx.apps.googleusercontent.com
GOOGLE_SECRET=GOCSPX-xxx
GOOGLE_REDIRECT_URI=http://localhost:8787/auth/google
E2E_GMAIL_ACCOUNT=your-testing-account@gmail.com
E2E_GMAIL_PASSWORD=your-password-here

# E2E tests read from .env (Playwright config)
# E2E_GMAIL_PASSWORD=your-password-here
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pnpm-debug.log*
# Build outputs
dist/
build/
public/

# Test artifacts
test-results/
Expand All @@ -27,6 +28,7 @@ playwright/.cache/
.env.local
.env.*.local
.env.production
.dev.vars
rcf/

# Windows Store app package directories and files
Expand All @@ -44,6 +46,9 @@ _pkginfo.txt
# but keep track of directories ending in .cache
!?*.[Cc]ache/

# Wrangler / Cloudflare
.wrangler/

# Others
ClientBin/
~$*
Expand Down
13 changes: 0 additions & 13 deletions docker-compose.yml

This file was deleted.

1 change: 0 additions & 1 deletion docker-entrypoint-initdb.d/create-test-db.sql

This file was deleted.

4 changes: 1 addition & 3 deletions drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { defineConfig } from "drizzle-kit";
import { dbCredentials } from "./hono/db/config.ts";

export default defineConfig({
schema: "./hono/db/schema.ts",
out: "./hono/db/migrations",
dialect: "postgresql",
dbCredentials,
dialect: "sqlite",
});
66 changes: 29 additions & 37 deletions hono/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { serveStatic } from "@hono/node-server/serve-static";
import { createNodeWebSocket } from "@hono/node-ws";
import { Hono } from "hono";
import { upgradeWebSocket } from "hono/cloudflare-workers";
import { csrf } from "hono/csrf";
import type { MiddlewareHandler } from "hono/types";
import type { WSContext } from "hono/ws";
Expand All @@ -13,66 +12,59 @@ import { index } from "./routes/index.js";
import { messagesRoute } from "./routes/messages.js";
import { testAuth } from "./routes/testAuth.js";
import { createWsRoute } from "./routes/ws.js";
import type { Variables } from "./types.js";
import type { Bindings, Variables } from "./types.js";

type AppOptions = {
sessionMiddleware?: MiddlewareHandler<{ Variables: Variables }>;
sessionMiddleware?: MiddlewareHandler<{ Bindings: Bindings; Variables: Variables }>;
};

export function createApp(options?: AppOptions) {
const app = new Hono<{ Variables: Variables }>();

// Set up session middleware
if (options?.sessionMiddleware) {
app.use("*", options.sessionMiddleware);
} else {
const store = new CookieStore();
app.use(
"*",
sessionMiddleware({
function createLazySessionMiddleware(): MiddlewareHandler<{ Bindings: Bindings; Variables: Variables }> {
let cached: MiddlewareHandler | null = null;
return async (c, next) => {
if (!cached) {
const store = new CookieStore();
cached = sessionMiddleware({
store,
encryptionKey: process.env.SESSION_SECRET || "your-super-secret-key-change-in-production",
encryptionKey: c.env.SESSION_SECRET || "your-super-secret-key-change-in-production",
expireAfterSeconds: 3600,
cookieOptions: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
secure: c.env.NODE_ENV === "production",
sameSite: "lax",
Comment on lines +28 to 33
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SESSION_SECRET falls back to a hard-coded default encryption key. If this ever runs outside local dev/tests (or if secrets are misconfigured), sessions become forgeable. Prefer failing fast when c.env.SESSION_SECRET is missing (at least when NODE_ENV === "production") rather than using a default.

See below for a potential fix:

      const isProduction = c.env.NODE_ENV === "production";
      const sessionSecret = c.env.SESSION_SECRET;

      if (!sessionSecret && isProduction) {
        throw new Error("SESSION_SECRET must be set in production");
      }

      let encryptionKey = sessionSecret;

      if (!encryptionKey) {
        const randomBytes = new Uint8Array(32);
        crypto.getRandomValues(randomBytes);
        encryptionKey = btoa(String.fromCharCode(...randomBytes));
      }

      const store = new CookieStore();
      cached = sessionMiddleware({
        store,
        encryptionKey,
        expireAfterSeconds: 3600,
        cookieOptions: {
          httpOnly: true,
          secure: isProduction,

Copilot uses AI. Check for mistakes.
path: "/",
},
}),
);
}

// Create WebSocket helper
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
});
}
const mw = cached as MiddlewareHandler;
return mw(c, next);
};
}

// Store connected WebSocket clients mapped to their user info
const clients = new Map<WSContext, { userEmail: string }>();
export function createApp(options?: AppOptions) {
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>();

// Serve static files from components directory
app.use("/components/*", serveStatic({ root: "./hono" }));
if (options?.sessionMiddleware) {
app.use("*", options.sessionMiddleware);
} else {
app.use("*", createLazySessionMiddleware());
}

// Serve static files from static directory
app.use("/static/*", serveStatic({ root: "./hono" }));
const clients = new Map<WSContext, { userEmail: string }>();

app.use("*", csrf());

// Register route handlers
app.route("/", health);
app.route("/", auth);
app.route("/", emailAuth);
app.route("/", channelsRoute);
app.route("/", messagesRoute);
app.route("/", index);
app.route("/", createWsRoute(upgradeWebSocket, clients));
app.route("/", testAuth);
Comment on lines 61 to +63
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testAuth is now always registered. With the current wrangler.toml setting NODE_ENV="development", this would expose the E2E login/logout endpoints in production. Consider registering testAuth only when the request env indicates development (e.g. mount a guard middleware for /test/* that returns 404 unless c.env.NODE_ENV === "development") and ensure production vars cannot accidentally enable it.

Copilot uses AI. Check for mistakes.

if (process.env.NODE_ENV === "development") {
app.route("/", testAuth);
}

return { app, injectWebSocket };
return { app };
}

const { app, injectWebSocket } = createApp();
const { app } = createApp();

export { app, injectWebSocket };
export { app };
4 changes: 2 additions & 2 deletions hono/auth/requireUser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createMiddleware } from "hono/factory";
import type { User, Variables } from "../types.js";
import type { Bindings, User, Variables } from "../types.js";

export const requireUser = createMiddleware<{ Variables: Variables }>(async (c, next) => {
export const requireUser = createMiddleware<{ Bindings: Bindings; Variables: Variables }>(async (c, next) => {
const session = c.get("session");
const user = session.get("user") as User | undefined;

Expand Down
8 changes: 0 additions & 8 deletions hono/db/config.ts

This file was deleted.

16 changes: 11 additions & 5 deletions hono/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { dbCredentials } from "./config.js";
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./schema.js";

const pool = new Pool(dbCredentials);
let cachedDb: ReturnType<typeof drizzle<typeof schema>> | null = null;
let cachedD1: D1Database | null = null;

export const db = drizzle(pool, { schema });
export function getDb(d1: D1Database) {
if (cachedDb && cachedD1 === d1) {
return cachedDb;
}
cachedD1 = d1;
cachedDb = drizzle(d1, { schema });
return cachedDb;
}

export * from "./schema.js";
38 changes: 38 additions & 0 deletions hono/db/migrations/0000_certain_omega_red.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
CREATE TABLE `channel_members` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`channel_id` integer NOT NULL,
`user_email` text NOT NULL,
`joined_at` text DEFAULT (CURRENT_TIMESTAMP),
FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
CREATE UNIQUE INDEX `channel_members_channel_id_user_email_unique` ON `channel_members` (`channel_id`,`user_email`);--> statement-breakpoint
CREATE TABLE `channels` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`created_by_email` text NOT NULL,
`created_at` text DEFAULT (CURRENT_TIMESTAMP)
);
--> statement-breakpoint
CREATE UNIQUE INDEX `channels_name_unique` ON `channels` (`name`);--> statement-breakpoint
CREATE TABLE `messages` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`content` text NOT NULL,
`user_email` text NOT NULL,
`user_name` text,
`channel_id` integer NOT NULL,
`created_at` text DEFAULT (CURRENT_TIMESTAMP),
FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
CREATE INDEX `messages_created_at_idx` ON `messages` (`created_at`);--> statement-breakpoint
CREATE INDEX `messages_channel_id_created_at_idx` ON `messages` (`channel_id`,`created_at`);--> statement-breakpoint
CREATE TABLE `users` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`email` text NOT NULL,
`name` text NOT NULL,
`password_hash` text NOT NULL,
`created_at` text DEFAULT (CURRENT_TIMESTAMP)
);
--> statement-breakpoint
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
7 changes: 0 additions & 7 deletions hono/db/migrations/0000_oval_moon_knight.sql

This file was deleted.

8 changes: 0 additions & 8 deletions hono/db/migrations/0001_nosy_rawhide_kid.sql

This file was deleted.

1 change: 0 additions & 1 deletion hono/db/migrations/0002_bent_maria_hill.sql

This file was deleted.

27 changes: 0 additions & 27 deletions hono/db/migrations/0003_narrow_maggott.sql

This file was deleted.

1 change: 0 additions & 1 deletion hono/db/migrations/0004_spotty_christian_walker.sql

This file was deleted.

Loading
Loading