From 79141198a962eed2266f9f50414c81760aee78b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:56:31 +0000 Subject: [PATCH 1/4] chore(deps): bump drizzle-orm Bumps [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) from 1.0.0-beta.9-e89174b to 1.0.0-beta.15-859cf75. - [Release notes](https://github.com/drizzle-team/drizzle-orm/releases) - [Commits](https://github.com/drizzle-team/drizzle-orm/commits) --- updated-dependencies: - dependency-name: drizzle-orm dependency-version: 1.0.0-beta.15-859cf75 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- bun.lock | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 68a1ea08..2842a924 100644 --- a/bun.lock +++ b/bun.lock @@ -42,7 +42,7 @@ "date-fns": "^4.1.0", "dither-plugin": "^1.1.1", "dotenv": "^17.2.4", - "drizzle-orm": "^1.0.0-beta.12-a5629fb", + "drizzle-orm": "^1.0.0-beta.15-859cf75", "es-toolkit": "^1.44.0", "hono": "^4.11.9", "hono-openapi": "^1.2.0", @@ -1074,7 +1074,7 @@ "drizzle-kit": ["drizzle-kit@1.0.0-beta.15-859cf75", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-Y36s1XQGVb1PgU3aRNgufp1K3D2VkIifu8kv4Ubsmxi+Dq+N7KMklnpp7Knu/XC4FZi2MHPPG3v3o097r0/TcQ=="], - "drizzle-orm": ["drizzle-orm@1.0.0-beta.9-e89174b", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-B5KR/qYMZ0JMOurK+0xi1ObpOQcgrjaC9wHUiU2eTJjLemuh2CoQIw6yur68NxZG6Xcd0b9qghUNC/78/bEfbg=="], + "drizzle-orm": ["drizzle-orm@1.0.0-beta.15-859cf75", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sinclair/typebox": ">=0.34.8", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "arktype": ">=2.0.0", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5", "typebox": ">=1.0.0", "valibot": ">=1.0.0-beta.7", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sinclair/typebox", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "arktype", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3", "typebox", "valibot", "zod"] }, "sha512-dGVb2Q70H2AV6513hkOXR3Ud0FeGXLdugVq3YehoqkGIVTJrkuo0gRnCcW/dfI00O07t3T4HSh4clF/D/o/IsQ=="], "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], diff --git a/package.json b/package.json index 0d4bdf6c..33901838 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "date-fns": "^4.1.0", "dither-plugin": "^1.1.1", "dotenv": "^17.2.4", - "drizzle-orm": "^1.0.0-beta.12-a5629fb", + "drizzle-orm": "^1.0.0-beta.15-859cf75", "es-toolkit": "^1.44.0", "hono": "^4.11.9", "hono-openapi": "^1.2.0", From bff9cfdb9c7fa84325455f1f45a2a3a8a134270d Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 14 Feb 2026 14:09:07 +0100 Subject: [PATCH 2/4] refactor: convert async transactions to sync + .run() --- .../cli/commands/assign-organization.ts | 10 +-- app/server/cli/commands/change-username.ts | 6 +- app/server/cli/commands/disable-2fa.ts | 6 +- app/server/cli/commands/rekey-2fa.ts | 49 +++++++---- app/server/cli/commands/reset-password.ts | 15 ++-- .../auth-middlewares/convert-legacy-user.ts | 81 +++++++++++-------- app/server/lib/auth.ts | 10 +-- app/server/modules/backups/backups.service.ts | 17 ++-- 8 files changed, 110 insertions(+), 84 deletions(-) diff --git a/app/server/cli/commands/assign-organization.ts b/app/server/cli/commands/assign-organization.ts index ee32f323..57deab32 100644 --- a/app/server/cli/commands/assign-organization.ts +++ b/app/server/cli/commands/assign-organization.ts @@ -38,20 +38,20 @@ const assignUserToOrganization = async (userId: string, organizationId: string) const existingMembership = await db.query.member.findFirst({ where: { userId } }); - await db.transaction(async (tx) => { + db.transaction((tx) => { if (existingMembership) { - await tx.update(member).set({ organizationId }).where(eq(member.id, existingMembership.id)); + tx.update(member).set({ organizationId }).where(eq(member.id, existingMembership.id)).run(); } else { - await tx.insert(member).values({ + tx.insert(member).values({ id: Bun.randomUUIDv7(), organizationId, userId, role: "member", createdAt: new Date(), - }); + }).run(); } - await tx.delete(sessionsTable).where(eq(sessionsTable.userId, userId)); + tx.delete(sessionsTable).where(eq(sessionsTable.userId, userId)).run(); }); }; diff --git a/app/server/cli/commands/change-username.ts b/app/server/cli/commands/change-username.ts index 7e13bb61..d45246bb 100644 --- a/app/server/cli/commands/change-username.ts +++ b/app/server/cli/commands/change-username.ts @@ -30,9 +30,9 @@ const changeUsername = async (oldUsername: string, newUsername: string) => { ); } - await db.transaction(async (tx) => { - await tx.update(usersTable).set({ username: normalizedUsername }).where(eq(usersTable.id, user.id)); - await tx.delete(sessionsTable).where(eq(sessionsTable.userId, user.id)); + db.transaction((tx) => { + tx.update(usersTable).set({ username: normalizedUsername }).where(eq(usersTable.id, user.id)).run(); + tx.delete(sessionsTable).where(eq(sessionsTable.userId, user.id)).run(); }); }; diff --git a/app/server/cli/commands/disable-2fa.ts b/app/server/cli/commands/disable-2fa.ts index f4de9046..667e843f 100644 --- a/app/server/cli/commands/disable-2fa.ts +++ b/app/server/cli/commands/disable-2fa.ts @@ -20,9 +20,9 @@ const disable2FA = async (username: string) => { throw new Error(`User "${username}" does not have 2FA enabled`); } - await db.transaction(async (tx) => { - await tx.update(usersTable).set({ twoFactorEnabled: false }).where(eq(usersTable.id, user.id)); - await tx.delete(twoFactor).where(eq(twoFactor.userId, user.id)); + db.transaction((tx) => { + tx.update(usersTable).set({ twoFactorEnabled: false }).where(eq(usersTable.id, user.id)).run(); + tx.delete(twoFactor).where(eq(twoFactor.userId, user.id)).run(); }); }; diff --git a/app/server/cli/commands/rekey-2fa.ts b/app/server/cli/commands/rekey-2fa.ts index bd49aa67..618a06aa 100644 --- a/app/server/cli/commands/rekey-2fa.ts +++ b/app/server/cli/commands/rekey-2fa.ts @@ -47,36 +47,51 @@ const resolveLegacySecret = async (options: { legacySecret?: string; legacySecre const rekeyTwoFactor = async (legacySecret: string) => { const legacyAuthSecret = await deriveSecretFromBase(legacySecret, "better-auth"); const currentAuthSecret = await cryptoUtils.deriveSecret("better-auth"); + const records = await db.query.twoFactor.findMany({}); + const errors: Array<{ userId: string; error: string }> = []; + const updates: Array<{ id: string; userId: string; secret: string; backupCodes: string }> = []; - return db.transaction(async (tx) => { - const records = await tx.query.twoFactor.findMany({}); - const errors: Array<{ userId: string; error: string }> = []; - let updated = 0; + for (const record of records) { + try { + const decryptedSecret = await symmetricDecrypt({ key: legacyAuthSecret, data: record.secret }); + const decryptedBackupCodes = await symmetricDecrypt({ + key: legacyAuthSecret, + data: record.backupCodes, + }); + + updates.push({ + id: record.id, + userId: record.userId, + secret: await symmetricEncrypt({ key: currentAuthSecret, data: decryptedSecret }), + backupCodes: await symmetricEncrypt({ key: currentAuthSecret, data: decryptedBackupCodes }), + }); + } catch (error) { + errors.push({ userId: record.userId, error: toMessage(error) }); + } + } - for (const record of records) { - try { - const decryptedSecret = await symmetricDecrypt({ key: legacyAuthSecret, data: record.secret }); - const decryptedBackupCodes = await symmetricDecrypt({ - key: legacyAuthSecret, - data: record.backupCodes, - }); + let updated = 0; - await tx + db.transaction((tx) => { + for (const record of updates) { + try { + tx .update(twoFactor) .set({ - secret: await symmetricEncrypt({ key: currentAuthSecret, data: decryptedSecret }), - backupCodes: await symmetricEncrypt({ key: currentAuthSecret, data: decryptedBackupCodes }), + secret: record.secret, + backupCodes: record.backupCodes, }) - .where(eq(twoFactor.id, record.id)); + .where(eq(twoFactor.id, record.id)) + .run(); updated += 1; } catch (error) { errors.push({ userId: record.userId, error: toMessage(error) }); } } - - return { total: records.length, updated, errors }; }); + + return { total: records.length, updated, errors }; }; export const rekey2FACommand = new Command("rekey-2fa") diff --git a/app/server/cli/commands/reset-password.ts b/app/server/cli/commands/reset-password.ts index 0306f802..b86fbe00 100644 --- a/app/server/cli/commands/reset-password.ts +++ b/app/server/cli/commands/reset-password.ts @@ -18,19 +18,20 @@ const resetPassword = async (username: string, newPassword: string) => { } const newPasswordHash = await hashPassword(newPassword); + const legacyHash = user.passwordHash ? await Bun.password.hash(newPassword) : null; - await db.transaction(async (tx) => { - await tx + db.transaction((tx) => { + tx .update(account) .set({ password: newPasswordHash }) - .where(and(eq(account.userId, user.id), eq(account.providerId, "credential"))); + .where(and(eq(account.userId, user.id), eq(account.providerId, "credential"))) + .run(); - if (user.passwordHash) { - const legacyHash = await Bun.password.hash(newPassword); - await tx.update(usersTable).set({ passwordHash: legacyHash }).where(eq(usersTable.id, user.id)); + if (legacyHash) { + tx.update(usersTable).set({ passwordHash: legacyHash }).where(eq(usersTable.id, user.id)).run(); } - await tx.delete(sessionsTable).where(eq(sessionsTable.userId, user.id)); + tx.delete(sessionsTable).where(eq(sessionsTable.userId, user.id)).run(); }); }; diff --git a/app/server/lib/auth-middlewares/convert-legacy-user.ts b/app/server/lib/auth-middlewares/convert-legacy-user.ts index f3c2d236..d21fcf6c 100644 --- a/app/server/lib/auth-middlewares/convert-legacy-user.ts +++ b/app/server/lib/auth-middlewares/convert-legacy-user.ts @@ -27,20 +27,45 @@ export const convertLegacyUserOnFirstLogin = async (ctx: AuthMiddlewareContext) const isValid = await Bun.password.verify(body.password, legacyUser.passwordHash ?? ""); if (isValid) { - await db.transaction(async (tx) => { - const newUserId = crypto.randomUUID(); - const accountId = crypto.randomUUID(); + const newUserId = crypto.randomUUID(); + const accountId = crypto.randomUUID(); - const oldMembership = await tx.query.member.findFirst({ - where: { userId: legacyUser.id }, - with: { - organization: true, + const oldMembership = await db.query.member.findFirst({ + where: { userId: legacyUser.id }, + with: { + organization: true, + }, + }); + + const passwordHash = await hashPassword(body.password); + + let newOrganizationData: { + id: string; + name: string; + slug: string; + createdAt: Date; + metadata: { + resticPassword: string; + }; + } | null = null; + + if (!oldMembership?.organization) { + const resticPassword = cryptoUtils.generateResticPassword(); + newOrganizationData = { + id: Bun.randomUUIDv7(), + name: `${legacyUser.name}'s Workspace`, + slug: legacyUser.email.split("@")[0] + "-" + Math.random().toString(36).slice(-4), + createdAt: new Date(), + metadata: { + resticPassword: await cryptoUtils.sealSecret(resticPassword), }, - }); + }; + } - await tx.delete(usersTable).where(eq(usersTable.id, legacyUser.id)); + db.transaction((tx) => { + tx.delete(usersTable).where(eq(usersTable.id, legacyUser.id)).run(); - await tx.insert(usersTable).values({ + tx.insert(usersTable).values({ id: newUserId, username: legacyUser.username, email: legacyUser.email, @@ -48,51 +73,37 @@ export const convertLegacyUserOnFirstLogin = async (ctx: AuthMiddlewareContext) hasDownloadedResticPassword: legacyUser.hasDownloadedResticPassword, emailVerified: false, role: "admin", // In legacy system, the only user is an admin - }); + }).run(); - await tx.insert(account).values({ + tx.insert(account).values({ id: accountId, providerId: "credential", accountId: legacyUser.username, userId: newUserId, - password: await hashPassword(body.password), + password: passwordHash, createdAt: new Date(), - }); + }).run(); // Migrate organization membership to the new user // The old membership was cascade-deleted when the old user was deleted if (oldMembership?.organization) { - await tx.insert(member).values({ + tx.insert(member).values({ id: Bun.randomUUIDv7(), userId: newUserId, organizationId: oldMembership.organization.id, role: oldMembership.role, createdAt: new Date(), - }); - } else { - const orgId = Bun.randomUUIDv7(); - const slug = legacyUser.email.split("@")[0] + "-" + Math.random().toString(36).slice(-4); - - const resticPassword = cryptoUtils.generateResticPassword(); - const metadata = { - resticPassword: await cryptoUtils.sealSecret(resticPassword), - }; - - await tx.insert(organization).values({ - id: orgId, - name: `${legacyUser.name}'s Workspace`, - slug: slug, - createdAt: new Date(), - metadata, - }); + }).run(); + } else if (newOrganizationData) { + tx.insert(organization).values(newOrganizationData).run(); - await tx.insert(member).values({ + tx.insert(member).values({ id: Bun.randomUUIDv7(), userId: newUserId, - organizationId: orgId, + organizationId: newOrganizationData.id, role: "owner", createdAt: new Date(), - }); + }).run(); } }); } else { diff --git a/app/server/lib/auth.ts b/app/server/lib/auth.ts index b1680b54..414e7576 100644 --- a/app/server/lib/auth.ts +++ b/app/server/lib/auth.ts @@ -84,24 +84,24 @@ export const auth = betterAuth({ }; try { - await db.transaction(async (tx) => { + db.transaction((tx) => { const orgId = Bun.randomUUIDv7(); - await tx.insert(organizationTable).values({ + tx.insert(organizationTable).values({ name: `${user.name}'s Workspace`, slug: slug, id: orgId, createdAt: new Date(), metadata, - }); + }).run(); - await tx.insert(member).values({ + tx.insert(member).values({ id: Bun.randomUUIDv7(), userId: user.id, role: "owner", organizationId: orgId, createdAt: new Date(), - }); + }).run(); }); } catch { await db diff --git a/app/server/modules/backups/backups.service.ts b/app/server/modules/backups/backups.service.ts index 26a88707..901424bb 100644 --- a/app/server/modules/backups/backups.service.ts +++ b/app/server/modules/backups/backups.service.ts @@ -320,16 +320,15 @@ const reorderSchedules = async (scheduleIds: number[]) => { } } - await db.transaction(async (tx) => { + db.transaction((tx) => { const now = Date.now(); - await Promise.all( - scheduleIds.map((scheduleId, index) => - tx - .update(backupSchedulesTable) - .set({ sortOrder: index, updatedAt: now }) - .where(and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId))), - ), - ); + for (const [index, scheduleId] of scheduleIds.entries()) { + tx + .update(backupSchedulesTable) + .set({ sortOrder: index, updatedAt: now }) + .where(and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId))) + .run(); + } }); }; From a2ab88231527f5f794cd15fa0d7eb1011734a358 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 14 Feb 2026 14:12:43 +0100 Subject: [PATCH 3/4] chore: formatting --- .../cli/commands/assign-organization.ts | 16 +++-- app/server/cli/commands/rekey-2fa.ts | 3 +- app/server/cli/commands/reset-password.ts | 3 +- .../auth-middlewares/convert-legacy-user.ts | 70 ++++++++++-------- app/server/lib/auth.ts | 71 +++++++------------ app/server/modules/backups/backups.service.ts | 3 +- 6 files changed, 77 insertions(+), 89 deletions(-) diff --git a/app/server/cli/commands/assign-organization.ts b/app/server/cli/commands/assign-organization.ts index 57deab32..59fdab1b 100644 --- a/app/server/cli/commands/assign-organization.ts +++ b/app/server/cli/commands/assign-organization.ts @@ -42,13 +42,15 @@ const assignUserToOrganization = async (userId: string, organizationId: string) if (existingMembership) { tx.update(member).set({ organizationId }).where(eq(member.id, existingMembership.id)).run(); } else { - tx.insert(member).values({ - id: Bun.randomUUIDv7(), - organizationId, - userId, - role: "member", - createdAt: new Date(), - }).run(); + tx.insert(member) + .values({ + id: Bun.randomUUIDv7(), + organizationId, + userId, + role: "member", + createdAt: new Date(), + }) + .run(); } tx.delete(sessionsTable).where(eq(sessionsTable.userId, userId)).run(); diff --git a/app/server/cli/commands/rekey-2fa.ts b/app/server/cli/commands/rekey-2fa.ts index 618a06aa..c52b8513 100644 --- a/app/server/cli/commands/rekey-2fa.ts +++ b/app/server/cli/commands/rekey-2fa.ts @@ -75,8 +75,7 @@ const rekeyTwoFactor = async (legacySecret: string) => { db.transaction((tx) => { for (const record of updates) { try { - tx - .update(twoFactor) + tx.update(twoFactor) .set({ secret: record.secret, backupCodes: record.backupCodes, diff --git a/app/server/cli/commands/reset-password.ts b/app/server/cli/commands/reset-password.ts index b86fbe00..fb9225fa 100644 --- a/app/server/cli/commands/reset-password.ts +++ b/app/server/cli/commands/reset-password.ts @@ -21,8 +21,7 @@ const resetPassword = async (username: string, newPassword: string) => { const legacyHash = user.passwordHash ? await Bun.password.hash(newPassword) : null; db.transaction((tx) => { - tx - .update(account) + tx.update(account) .set({ password: newPasswordHash }) .where(and(eq(account.userId, user.id), eq(account.providerId, "credential"))) .run(); diff --git a/app/server/lib/auth-middlewares/convert-legacy-user.ts b/app/server/lib/auth-middlewares/convert-legacy-user.ts index d21fcf6c..5be364d4 100644 --- a/app/server/lib/auth-middlewares/convert-legacy-user.ts +++ b/app/server/lib/auth-middlewares/convert-legacy-user.ts @@ -65,45 +65,53 @@ export const convertLegacyUserOnFirstLogin = async (ctx: AuthMiddlewareContext) db.transaction((tx) => { tx.delete(usersTable).where(eq(usersTable.id, legacyUser.id)).run(); - tx.insert(usersTable).values({ - id: newUserId, - username: legacyUser.username, - email: legacyUser.email, - name: legacyUser.name, - hasDownloadedResticPassword: legacyUser.hasDownloadedResticPassword, - emailVerified: false, - role: "admin", // In legacy system, the only user is an admin - }).run(); + tx.insert(usersTable) + .values({ + id: newUserId, + username: legacyUser.username, + email: legacyUser.email, + name: legacyUser.name, + hasDownloadedResticPassword: legacyUser.hasDownloadedResticPassword, + emailVerified: false, + role: "admin", // In legacy system, the only user is an admin + }) + .run(); - tx.insert(account).values({ - id: accountId, - providerId: "credential", - accountId: legacyUser.username, - userId: newUserId, - password: passwordHash, - createdAt: new Date(), - }).run(); + tx.insert(account) + .values({ + id: accountId, + providerId: "credential", + accountId: legacyUser.username, + userId: newUserId, + password: passwordHash, + createdAt: new Date(), + }) + .run(); // Migrate organization membership to the new user // The old membership was cascade-deleted when the old user was deleted if (oldMembership?.organization) { - tx.insert(member).values({ - id: Bun.randomUUIDv7(), - userId: newUserId, - organizationId: oldMembership.organization.id, - role: oldMembership.role, - createdAt: new Date(), - }).run(); + tx.insert(member) + .values({ + id: Bun.randomUUIDv7(), + userId: newUserId, + organizationId: oldMembership.organization.id, + role: oldMembership.role, + createdAt: new Date(), + }) + .run(); } else if (newOrganizationData) { tx.insert(organization).values(newOrganizationData).run(); - tx.insert(member).values({ - id: Bun.randomUUIDv7(), - userId: newUserId, - organizationId: newOrganizationData.id, - role: "owner", - createdAt: new Date(), - }).run(); + tx.insert(member) + .values({ + id: Bun.randomUUIDv7(), + userId: newUserId, + organizationId: newOrganizationData.id, + role: "owner", + createdAt: new Date(), + }) + .run(); } }); } else { diff --git a/app/server/lib/auth.ts b/app/server/lib/auth.ts index 414e7576..58f0bd6e 100644 --- a/app/server/lib/auth.ts +++ b/app/server/lib/auth.ts @@ -6,32 +6,19 @@ import { type MiddlewareOptions, } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; -import { - admin, - createAuthMiddleware, - twoFactor, - username, - organization, -} from "better-auth/plugins"; +import { admin, createAuthMiddleware, twoFactor, username, organization } from "better-auth/plugins"; import { UnauthorizedError } from "http-errors-enhanced"; import { convertLegacyUserOnFirstLogin } from "./auth-middlewares/convert-legacy-user"; import { eq } from "drizzle-orm"; import { config } from "../core/config"; import { db } from "../db/db"; import { cryptoUtils } from "../utils/crypto"; -import { - organization as organizationTable, - member, - usersTable, -} from "../db/schema"; +import { organization as organizationTable, member, usersTable } from "../db/schema"; import { ensureOnlyOneUser } from "./auth-middlewares/only-one-user"; import { authService } from "../modules/auth/auth.service"; import { tanstackStartCookies } from "better-auth/tanstack-start"; -export type AuthMiddlewareContext = MiddlewareContext< - MiddlewareOptions, - AuthContext ->; +export type AuthMiddlewareContext = MiddlewareContext>; export const auth = betterAuth({ secret: await cryptoUtils.deriveSecret("better-auth"), @@ -72,45 +59,41 @@ export const auth = betterAuth({ return { data: user }; }, after: async (user) => { - const slug = - user.email.split("@")[0] + - "-" + - Math.random().toString(36).slice(-4); + const slug = user.email.split("@")[0] + "-" + Math.random().toString(36).slice(-4); const resticPassword = cryptoUtils.generateResticPassword(); const metadata = { - resticPassword: - await cryptoUtils.sealSecret(resticPassword), + resticPassword: await cryptoUtils.sealSecret(resticPassword), }; try { db.transaction((tx) => { const orgId = Bun.randomUUIDv7(); - tx.insert(organizationTable).values({ - name: `${user.name}'s Workspace`, - slug: slug, - id: orgId, - createdAt: new Date(), - metadata, - }).run(); + tx.insert(organizationTable) + .values({ + name: `${user.name}'s Workspace`, + slug: slug, + id: orgId, + createdAt: new Date(), + metadata, + }) + .run(); - tx.insert(member).values({ - id: Bun.randomUUIDv7(), - userId: user.id, - role: "owner", - organizationId: orgId, - createdAt: new Date(), - }).run(); + tx.insert(member) + .values({ + id: Bun.randomUUIDv7(), + userId: user.id, + role: "owner", + organizationId: orgId, + createdAt: new Date(), + }) + .run(); }); } catch { - await db - .delete(usersTable) - .where(eq(usersTable.id, user.id)); + await db.delete(usersTable).where(eq(usersTable.id, user.id)); - throw new Error( - `Failed to create organization for user ${user.id}`, - ); + throw new Error(`Failed to create organization for user ${user.id}`); } }, }, @@ -123,9 +106,7 @@ export const auth = betterAuth({ }); if (!orgMembership) { - throw new UnauthorizedError( - "User does not belong to any organization", - ); + throw new UnauthorizedError("User does not belong to any organization"); } return { diff --git a/app/server/modules/backups/backups.service.ts b/app/server/modules/backups/backups.service.ts index 901424bb..99cf88a7 100644 --- a/app/server/modules/backups/backups.service.ts +++ b/app/server/modules/backups/backups.service.ts @@ -323,8 +323,7 @@ const reorderSchedules = async (scheduleIds: number[]) => { db.transaction((tx) => { const now = Date.now(); for (const [index, scheduleId] of scheduleIds.entries()) { - tx - .update(backupSchedulesTable) + tx.update(backupSchedulesTable) .set({ sortOrder: index, updatedAt: now }) .where(and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId))) .run(); From 42e2244a235c77d68c1c82a5ce2a9ad95fd3fdb7 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 14 Feb 2026 14:15:06 +0100 Subject: [PATCH 4/4] chore: add lefthook dep --- bun.lock | 23 +++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 25 insertions(+) diff --git a/bun.lock b/bun.lock index 2842a924..8582afd6 100644 --- a/bun.lock +++ b/bun.lock @@ -88,6 +88,7 @@ "bun-types": "^1.3.6", "dotenv-cli": "^11.0.0", "drizzle-kit": "^1.0.0-beta.15-859cf75", + "lefthook": "^2.1.1", "lightningcss": "^1.31.1", "nitro": "^3.0.1-alpha.2", "oxfmt": "^0.31.0", @@ -1234,6 +1235,28 @@ "kysely": ["kysely@0.28.11", "", {}, "sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg=="], + "lefthook": ["lefthook@2.1.1", "", { "optionalDependencies": { "lefthook-darwin-arm64": "2.1.1", "lefthook-darwin-x64": "2.1.1", "lefthook-freebsd-arm64": "2.1.1", "lefthook-freebsd-x64": "2.1.1", "lefthook-linux-arm64": "2.1.1", "lefthook-linux-x64": "2.1.1", "lefthook-openbsd-arm64": "2.1.1", "lefthook-openbsd-x64": "2.1.1", "lefthook-windows-arm64": "2.1.1", "lefthook-windows-x64": "2.1.1" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-Tl9h9c+sG3ShzTHKuR3LAIblnnh+Mgxnm2Ul7yu9cu260Z27LEbO3V6Zw4YZFP59/2rlD42pt/llYsQCkkCFzw=="], + + "lefthook-darwin-arm64": ["lefthook-darwin-arm64@2.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-O/RS1j03/Fnq5zCzEb2r7UOBsqPeBuf1C5pMkIJcO4TSE6hf3rhLUkcorKc2M5ni/n5zLGtzQUXHV08/fSAT3Q=="], + + "lefthook-darwin-x64": ["lefthook-darwin-x64@2.1.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-mm/kdKl81ROPoYnj9XYk5JDqj+/6Al8w/SSPDfhItkLJyl4pqS+hWUOP6gDGrnuRk8S0DvJ2+hzhnDsQnZohWQ=="], + + "lefthook-freebsd-arm64": ["lefthook-freebsd-arm64@2.1.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-F7JXlKmjxGqGbCWPLND0bVB4DMQezIe48pEwTlUQZbxh450c2gP5Q8FdttMZKOT163kBGGTqJAJSEC6zW+QSxA=="], + + "lefthook-freebsd-x64": ["lefthook-freebsd-x64@2.1.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Po8/lJMqNzKSZPuEI46dLuWoBoXtAxCuRpeOh6DAV/M4RhBynaCu8rLMZ9BqF7cVbZEWoplOmYo6HdOuiYpCkQ=="], + + "lefthook-linux-arm64": ["lefthook-linux-arm64@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mI2ljFgPEqHxI8vrN9nKgnVu63Rz1KisDbPwlvs7BTYNwq3sncdK5ukpGR4zzWdh6saNJ5tCtHEtep5GQI11nw=="], + + "lefthook-linux-x64": ["lefthook-linux-x64@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-m3G/FaxC+crxeg9XeaUuHfEoL+i9gbkg2Hp2KD2IcVVIxprqlyqf0Hb8zbLV2NMXuo5RSGokJu44oAoTO3Ou2g=="], + + "lefthook-openbsd-arm64": ["lefthook-openbsd-arm64@2.1.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-gz/8FJPvhjOdOFt1GmFvuvDOe+W+BBRjoeAT1/mTgkN7HCXMXgqNjjvakQKQeGz1I1v08wXG1ZNf5y+T9XBCDQ=="], + + "lefthook-openbsd-x64": ["lefthook-openbsd-x64@2.1.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-ch3lyMUtbmtWUufaQVn4IoEs/2hjK51XqaCdY1mh5ca//VctR1peknIwQ5feHu+vATCDviWQ7HsdNDewm3HMPg=="], + + "lefthook-windows-arm64": ["lefthook-windows-arm64@2.1.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mm3PZhKDs9FE/jQDimkfWxtoj9xQ2k8uw2MdhtC825bhvIh+MEi0WFj/MOW+ug0RBg0I55tGYzZ5aVuozAWpTQ=="], + + "lefthook-windows-x64": ["lefthook-windows-x64@2.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-1L2oGIzmhfOTxfwbe5mpSQ+m3ilpvGNymwIhn4UHq6hwHsUL6HEhODqx02GfBn6OXpVIr56bvdBAusjL/SVYGQ=="], + "libsql": ["libsql@0.5.22", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.22", "@libsql/darwin-x64": "0.5.22", "@libsql/linux-arm-gnueabihf": "0.5.22", "@libsql/linux-arm-musleabihf": "0.5.22", "@libsql/linux-arm64-gnu": "0.5.22", "@libsql/linux-arm64-musl": "0.5.22", "@libsql/linux-x64-gnu": "0.5.22", "@libsql/linux-x64-musl": "0.5.22", "@libsql/win32-x64-msvc": "0.5.22" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA=="], "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], diff --git a/package.json b/package.json index 33901838..3ac253a8 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "private": true, "type": "module", "scripts": { + "postinstall": "lefthook install", "lint": "oxlint --type-aware", "dev": "NODE_ENV=development bunx --bun vite", "build": "vite build", @@ -109,6 +110,7 @@ "bun-types": "^1.3.6", "dotenv-cli": "^11.0.0", "drizzle-kit": "^1.0.0-beta.15-859cf75", + "lefthook": "^2.1.1", "lightningcss": "^1.31.1", "nitro": "^3.0.1-alpha.2", "oxfmt": "^0.31.0",