diff --git a/Dockerfile b/Dockerfile index b100b1f6..8584cb28 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ ENV VITE_RESTIC_VERSION=${RESTIC_VERSION} \ RUN apk update --no-cache && \ apk upgrade --no-cache && \ - apk add --no-cache davfs2=1.6.1-r2 openssh-client fuse3 sshfs tini nfs-utils cifs-utils util-linux + apk add --no-cache davfs2=1.6.1-r2 openssh-client fuse3 sshfs tini cifs-utils util-linux ENTRYPOINT ["/sbin/tini", "-s", "--"] diff --git a/app/lib/auth.ts b/app/lib/auth.ts index 049295a2..f35ac450 100644 --- a/app/lib/auth.ts +++ b/app/lib/auth.ts @@ -14,36 +14,63 @@ import { ensureOnlyOneUser } from "./auth-middlewares/only-one-user"; export type AuthMiddlewareContext = MiddlewareContext>; -export const auth = betterAuth({ - secret: await cryptoUtils.deriveSecret("better-auth"), - hooks: { - before: createAuthMiddleware(async (ctx) => { - await ensureOnlyOneUser(ctx); - await convertLegacyUserOnFirstLogin(ctx); +const createBetterAuth = (secret: string) => + betterAuth({ + secret, + hooks: { + before: createAuthMiddleware(async (ctx) => { + await ensureOnlyOneUser(ctx); + await convertLegacyUserOnFirstLogin(ctx); + }), + }, + database: drizzleAdapter(db, { + provider: "sqlite", }), - }, - database: drizzleAdapter(db, { - provider: "sqlite", - }), - emailAndPassword: { - enabled: true, - }, - user: { - modelName: "usersTable", - additionalFields: { - username: { - type: "string", - returned: true, - required: true, - }, - hasDownloadedResticPassword: { - type: "boolean", - returned: true, + emailAndPassword: { + enabled: true, + }, + user: { + modelName: "usersTable", + additionalFields: { + username: { + type: "string", + returned: true, + required: true, + }, + hasDownloadedResticPassword: { + type: "boolean", + returned: true, + }, }, }, + session: { + modelName: "sessionsTable", + }, + plugins: [username({})], + }); + +type Auth = ReturnType; + +let _auth: Auth | null = null; + +const createAuth = async (): Promise => { + if (_auth) return _auth; + + _auth = createBetterAuth(await cryptoUtils.deriveSecret("better-auth")); + + return _auth; +}; + +export const auth = new Proxy( + {}, + { + get(_, prop, receiver) { + if (!_auth) { + throw new Error("Auth not initialized. Call initAuth() first."); + } + return Reflect.get(_auth, prop, receiver); + }, }, - session: { - modelName: "sessionsTable", - }, - plugins: [username({})], -}); +) as Auth; + +export const initAuth = createAuth; diff --git a/app/server/index.ts b/app/server/index.ts index 28bd6df4..8faf6a90 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -16,10 +16,10 @@ if (cliRun) { process.exit(0); } -const app = createApp(); - runDbMigrations(); +const app = createApp(); + await runMigrations(); await startup(); diff --git a/app/server/modules/lifecycle/startup.ts b/app/server/modules/lifecycle/startup.ts index 9d18aaa4..f7e5fadb 100644 --- a/app/server/modules/lifecycle/startup.ts +++ b/app/server/modules/lifecycle/startup.ts @@ -13,6 +13,8 @@ import { repositoriesService } from "../repositories/repositories.service"; import { notificationsService } from "../notifications/notifications.service"; import { VolumeAutoRemountJob } from "~/server/jobs/auto-remount"; import { cache } from "~/server/utils/cache"; +import { initAuth } from "~/lib/auth"; +import { toMessage } from "~/server/utils/errors"; const ensureLatestConfigurationSchema = async () => { const volumes = await db.query.volumesTable.findMany({}); @@ -50,6 +52,11 @@ export const startup = async () => { logger.error(`Error ensuring restic passfile exists: ${err.message}`); }); + await initAuth().catch((err) => { + logger.error(`Error initializing auth: ${toMessage(err)}`); + throw err; + }); + await ensureLatestConfigurationSchema(); const volumes = await db.query.volumesTable.findMany({ diff --git a/app/test/setup-client.ts b/app/test/setup-client.ts index f737ee3e..a0faa585 100644 --- a/app/test/setup-client.ts +++ b/app/test/setup-client.ts @@ -1,4 +1,4 @@ import "./setup.ts"; import { GlobalRegistrator } from "@happy-dom/global-registrator"; -GlobalRegistrator.register(); +GlobalRegistrator.register({ url: "http://localhost:3000" }); diff --git a/app/test/setup.ts b/app/test/setup.ts index 81927747..ea73e5b0 100644 --- a/app/test/setup.ts +++ b/app/test/setup.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { cwd } from "node:process"; import * as schema from "~/server/db/schema"; import { db, setSchema } from "~/server/db/db"; +import { initAuth } from "~/lib/auth"; setSchema(schema); @@ -27,4 +28,5 @@ void mock.module("~/server/utils/crypto", () => ({ beforeAll(async () => { const migrationsFolder = path.join(cwd(), "app", "drizzle"); migrate(db, { migrationsFolder }); + await initAuth(); });