fix: store profile picture in db to prevent header overload

This commit is contained in:
Aarnav Tale
2026-03-30 13:58:29 -04:00
parent 0191da4ce2
commit 838a2cd732
6 changed files with 332 additions and 9 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
import { AlertCircle } from "lucide-react"; import { AlertCircle } from "lucide-react";
import { useEffect, useState } from "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 Button from "~/components/button";
import Card from "~/components/card"; import Card from "~/components/card";
@@ -139,11 +139,11 @@ export default function Page({ loaderData, actionData }: Route.ComponentProps) {
</Button> </Button>
</Form> </Form>
{isOidcConnectorEnabled ? ( {isOidcConnectorEnabled ? (
<Link to="/oidc/start"> <RouterLink to="/oidc/start" prefetch="none" reloadDocument>
<Button className="mt-2 w-full" disabled={oidcErrorCodes.length > 0} variant="light"> <Button className="mt-2 w-full" disabled={oidcErrorCodes.length > 0} variant="light">
Single Sign-On Single Sign-On
</Button> </Button>
</Link> </RouterLink>
) : undefined} ) : undefined}
</Card> </Card>
</div> </div>
+1 -1
View File
@@ -103,6 +103,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
const userId = await context.auth.findOrCreateUser(claims.sub, { const userId = await context.auth.findOrCreateUser(claims.sub, {
name, name,
email: userInfo.email, email: userInfo.email,
picture,
}); });
try { try {
@@ -122,7 +123,6 @@ export async function loader({ request, context }: Route.LoaderArgs) {
name, name,
email: userInfo.email, email: userInfo.email,
username, username,
picture,
}), }),
}, },
}); });
+1
View File
@@ -24,6 +24,7 @@ export const users = sqliteTable("users", {
sub: text("sub").notNull().unique(), sub: text("sub").notNull().unique(),
name: text("name"), name: text("name"),
email: text("email"), email: text("email"),
picture: text("picture"),
role: text("role").notNull().default("member"), role: text("role").notNull().default("member"),
headscale_user_id: text("headscale_user_id").unique(), headscale_user_id: text("headscale_user_id").unique(),
created_at: integer("created_at", { mode: "timestamp" }).$default(() => new Date()), created_at: integer("created_at", { mode: "timestamp" }).$default(() => new Date()),
+8 -5
View File
@@ -51,7 +51,6 @@ interface CookiePayload {
name: string; name: string;
email?: string; email?: string;
username?: string; username?: string;
picture?: string;
}; };
} }
@@ -149,9 +148,11 @@ export class AuthService {
role, role,
headscaleUserId: user.headscale_user_id ?? undefined, headscaleUserId: user.headscale_user_id ?? undefined,
}, },
profile: payload.profile ?? { profile: {
name: user.name ?? user.sub, name: payload.profile?.name ?? user.name ?? user.sub,
email: user.email ?? undefined, email: payload.profile?.email ?? user.email ?? undefined,
username: payload.profile?.username,
picture: user.picture ?? undefined,
}, },
}; };
} }
@@ -278,7 +279,7 @@ export class AuthService {
*/ */
async findOrCreateUser( async findOrCreateUser(
subject: string, subject: string,
profile?: { name?: string; email?: string }, profile?: { name?: string; email?: string; picture?: string },
): Promise<string> { ): Promise<string> {
const [existing] = await this.opts.db const [existing] = await this.opts.db
.select() .select()
@@ -292,6 +293,7 @@ export class AuthService {
.set({ .set({
name: profile?.name, name: profile?.name,
email: profile?.email, email: profile?.email,
picture: profile?.picture,
last_login_at: new Date(), last_login_at: new Date(),
updated_at: new Date(), updated_at: new Date(),
}) })
@@ -305,6 +307,7 @@ export class AuthService {
sub: subject, sub: subject,
name: profile?.name, name: profile?.name,
email: profile?.email, email: profile?.email,
picture: profile?.picture,
role: "member", role: "member",
caps: capsForRole("member"), caps: capsForRole("member"),
}); });
@@ -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`;
@@ -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": []
}