From 838a2cd732931de5fe96e44e03ba3dba53e1e59f Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 30 Mar 2026 13:58:29 -0400 Subject: [PATCH] fix: store profile picture in db to prevent header overload --- app/routes/auth/login/page.tsx | 6 +- app/routes/auth/oidc-callback.ts | 2 +- app/server/db/schema.ts | 1 + app/server/web/auth.ts | 13 +- .../migration.sql | 22 ++ .../snapshot.json | 297 ++++++++++++++++++ 6 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 drizzle/20260330175441_add_user_picture/migration.sql create mode 100644 drizzle/20260330175441_add_user_picture/snapshot.json diff --git a/app/routes/auth/login/page.tsx b/app/routes/auth/login/page.tsx index 349866a..af43b41 100644 --- a/app/routes/auth/login/page.tsx +++ b/app/routes/auth/login/page.tsx @@ -1,6 +1,6 @@ import { AlertCircle } from "lucide-react"; import { useEffect, useState } from "react"; -import { Form, redirect, useSearchParams } from "react-router"; +import { Form, Link as RouterLink, redirect, useSearchParams } from "react-router"; import Button from "~/components/button"; import Card from "~/components/card"; @@ -139,11 +139,11 @@ export default function Page({ loaderData, actionData }: Route.ComponentProps) { {isOidcConnectorEnabled ? ( - + - + ) : undefined} diff --git a/app/routes/auth/oidc-callback.ts b/app/routes/auth/oidc-callback.ts index 0d3eaa1..761f62e 100644 --- a/app/routes/auth/oidc-callback.ts +++ b/app/routes/auth/oidc-callback.ts @@ -103,6 +103,7 @@ export async function loader({ request, context }: Route.LoaderArgs) { const userId = await context.auth.findOrCreateUser(claims.sub, { name, email: userInfo.email, + picture, }); try { @@ -122,7 +123,6 @@ export async function loader({ request, context }: Route.LoaderArgs) { name, email: userInfo.email, username, - picture, }), }, }); diff --git a/app/server/db/schema.ts b/app/server/db/schema.ts index 773b95c..ce8d5fc 100644 --- a/app/server/db/schema.ts +++ b/app/server/db/schema.ts @@ -24,6 +24,7 @@ export const users = sqliteTable("users", { sub: text("sub").notNull().unique(), name: text("name"), email: text("email"), + picture: text("picture"), role: text("role").notNull().default("member"), headscale_user_id: text("headscale_user_id").unique(), created_at: integer("created_at", { mode: "timestamp" }).$default(() => new Date()), diff --git a/app/server/web/auth.ts b/app/server/web/auth.ts index 9d790ad..539c252 100644 --- a/app/server/web/auth.ts +++ b/app/server/web/auth.ts @@ -51,7 +51,6 @@ interface CookiePayload { name: string; email?: string; username?: string; - picture?: string; }; } @@ -149,9 +148,11 @@ export class AuthService { role, headscaleUserId: user.headscale_user_id ?? undefined, }, - profile: payload.profile ?? { - name: user.name ?? user.sub, - email: user.email ?? undefined, + profile: { + name: payload.profile?.name ?? user.name ?? user.sub, + email: payload.profile?.email ?? user.email ?? undefined, + username: payload.profile?.username, + picture: user.picture ?? undefined, }, }; } @@ -278,7 +279,7 @@ export class AuthService { */ async findOrCreateUser( subject: string, - profile?: { name?: string; email?: string }, + profile?: { name?: string; email?: string; picture?: string }, ): Promise { const [existing] = await this.opts.db .select() @@ -292,6 +293,7 @@ export class AuthService { .set({ name: profile?.name, email: profile?.email, + picture: profile?.picture, last_login_at: new Date(), updated_at: new Date(), }) @@ -305,6 +307,7 @@ export class AuthService { sub: subject, name: profile?.name, email: profile?.email, + picture: profile?.picture, role: "member", caps: capsForRole("member"), }); diff --git a/drizzle/20260330175441_add_user_picture/migration.sql b/drizzle/20260330175441_add_user_picture/migration.sql new file mode 100644 index 0000000..a5fd32d --- /dev/null +++ b/drizzle/20260330175441_add_user_picture/migration.sql @@ -0,0 +1,22 @@ +ALTER TABLE `users` ADD `picture` text;--> statement-breakpoint +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_users` ( + `id` text PRIMARY KEY, + `sub` text NOT NULL UNIQUE, + `name` text, + `email` text, + `picture` text, + `role` text DEFAULT 'member' NOT NULL, + `headscale_user_id` text UNIQUE, + `created_at` integer, + `updated_at` integer, + `last_login_at` integer, + `caps` integer DEFAULT 0 NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_users`(`id`, `sub`, `name`, `email`, `role`, `headscale_user_id`, `created_at`, `updated_at`, `last_login_at`, `caps`) SELECT `id`, `sub`, `name`, `email`, `role`, `headscale_user_id`, `created_at`, `updated_at`, `last_login_at`, `caps` FROM `users`;--> statement-breakpoint +DROP TABLE `users`;--> statement-breakpoint +ALTER TABLE `__new_users` RENAME TO `users`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +DROP INDEX IF EXISTS `users_sub_unique`;--> statement-breakpoint +DROP INDEX IF EXISTS `users_headscale_user_id_unique`; \ No newline at end of file diff --git a/drizzle/20260330175441_add_user_picture/snapshot.json b/drizzle/20260330175441_add_user_picture/snapshot.json new file mode 100644 index 0000000..9bb26ff --- /dev/null +++ b/drizzle/20260330175441_add_user_picture/snapshot.json @@ -0,0 +1,297 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "b0533d83-6eb6-422f-925f-c59e4db89384", + "prevIds": ["6d6838a7-e7ab-4661-bc7d-b47566bfff13"], + "ddl": [ + { + "name": "auth_sessions", + "entityType": "tables" + }, + { + "name": "ephemeral_nodes", + "entityType": "tables" + }, + { + "name": "host_info", + "entityType": "tables" + }, + { + "name": "users", + "entityType": "tables" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "auth_sessions" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "kind", + "entityType": "columns", + "table": "auth_sessions" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "auth_sessions" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "api_key_hash", + "entityType": "columns", + "table": "auth_sessions" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "api_key_display", + "entityType": "columns", + "table": "auth_sessions" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "expires_at", + "entityType": "columns", + "table": "auth_sessions" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "created_at", + "entityType": "columns", + "table": "auth_sessions" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "auth_key", + "entityType": "columns", + "table": "ephemeral_nodes" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "node_key", + "entityType": "columns", + "table": "ephemeral_nodes" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "host_id", + "entityType": "columns", + "table": "host_info" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "payload", + "entityType": "columns", + "table": "host_info" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "updated_at", + "entityType": "columns", + "table": "host_info" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "users" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sub", + "entityType": "columns", + "table": "users" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "users" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "users" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "picture", + "entityType": "columns", + "table": "users" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": "'member'", + "generated": null, + "name": "role", + "entityType": "columns", + "table": "users" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "headscale_user_id", + "entityType": "columns", + "table": "users" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "created_at", + "entityType": "columns", + "table": "users" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "updated_at", + "entityType": "columns", + "table": "users" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "last_login_at", + "entityType": "columns", + "table": "users" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "caps", + "entityType": "columns", + "table": "users" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "auth_sessions_pk", + "table": "auth_sessions", + "entityType": "pks" + }, + { + "columns": ["auth_key"], + "nameExplicit": false, + "name": "ephemeral_nodes_pk", + "table": "ephemeral_nodes", + "entityType": "pks" + }, + { + "columns": ["host_id"], + "nameExplicit": false, + "name": "host_info_pk", + "table": "host_info", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "users_pk", + "table": "users", + "entityType": "pks" + }, + { + "columns": ["sub"], + "nameExplicit": false, + "name": "users_sub_unique", + "entityType": "uniques", + "table": "users" + }, + { + "columns": ["headscale_user_id"], + "nameExplicit": false, + "name": "users_headscale_user_id_unique", + "entityType": "uniques", + "table": "users" + } + ], + "renames": [] +}