refactor: convert async transactions to sync + .run()
This commit is contained in:
parent
79141198a9
commit
bff9cfdb9c
8 changed files with 110 additions and 84 deletions
|
|
@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
for (const record of records) {
|
||||
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) });
|
||||
}
|
||||
}
|
||||
|
||||
let updated = 0;
|
||||
|
||||
db.transaction((tx) => {
|
||||
for (const record of updates) {
|
||||
try {
|
||||
const decryptedSecret = await symmetricDecrypt({ key: legacyAuthSecret, data: record.secret });
|
||||
const decryptedBackupCodes = await symmetricDecrypt({
|
||||
key: legacyAuthSecret,
|
||||
data: record.backupCodes,
|
||||
});
|
||||
|
||||
await tx
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}).run();
|
||||
} else if (newOrganizationData) {
|
||||
tx.insert(organization).values(newOrganizationData).run();
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue