Merge branch 'tvarohohlavy-defer-auth-after-passfile'

This commit is contained in:
Nicolas Meienberger 2026-01-08 20:50:44 +01:00
commit 1866db1969
6 changed files with 69 additions and 33 deletions

View file

@ -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", "--"]

View file

@ -14,36 +14,63 @@ import { ensureOnlyOneUser } from "./auth-middlewares/only-one-user";
export type AuthMiddlewareContext = MiddlewareContext<MiddlewareOptions, AuthContext<BetterAuthOptions>>;
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<typeof createBetterAuth>;
let _auth: Auth | null = null;
const createAuth = async (): Promise<Auth> => {
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;

View file

@ -16,10 +16,10 @@ if (cliRun) {
process.exit(0);
}
const app = createApp();
runDbMigrations();
const app = createApp();
await runMigrations();
await startup();

View file

@ -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({

View file

@ -1,4 +1,4 @@
import "./setup.ts";
import { GlobalRegistrator } from "@happy-dom/global-registrator";
GlobalRegistrator.register();
GlobalRegistrator.register({ url: "http://localhost:3000" });

View file

@ -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();
});