anything-llm/server/models/passwordRecovery.js
Sean Hatfield 11f6419c3c
[FEAT] Implement new login screen UI & multi-user password reset (#1074)
* WIP new login screen UI

* update prisma schema/create new models for pw recovery

* WIP password recovery backend

* WIP reset password flow

* WIP pw reset flow

* password reset logic complete & functional UI

* WIP login screen redesign for single and multi user

* create placeholder modal to display recovery codes

* implement UI for recovery code modals/download recovery codes

* multiuser desktop password reset UI/functionality complete

* support single user mode for pw reset

* mobile styles for all password reset/login flows complete

* lint

* remove single user password recovery

* create PasswordRecovery util file to make more readable

* do not drop-replace users table in migration

* review pr

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
2024-04-25 16:52:30 -07:00

116 lines
3.4 KiB
JavaScript

const { v4 } = require("uuid");
const prisma = require("../utils/prisma");
const bcrypt = require("bcrypt");
const RecoveryCode = {
tablename: "recovery_codes",
writable: [],
create: async function (userId, code) {
try {
const codeHash = await bcrypt.hash(code, 10);
const recoveryCode = await prisma.recovery_codes.create({
data: { user_id: userId, code_hash: codeHash },
});
return { recoveryCode, error: null };
} catch (error) {
console.error("FAILED TO CREATE RECOVERY CODE.", error.message);
return { recoveryCode: null, error: error.message };
}
},
createMany: async function (data) {
try {
const recoveryCodes = await prisma.$transaction(
data.map((recoveryCode) =>
prisma.recovery_codes.create({ data: recoveryCode })
)
);
return { recoveryCodes, error: null };
} catch (error) {
console.error("FAILED TO CREATE RECOVERY CODES.", error.message);
return { recoveryCodes: null, error: error.message };
}
},
findFirst: async function (clause = {}) {
try {
const recoveryCode = await prisma.recovery_codes.findFirst({
where: clause,
});
return recoveryCode;
} catch (error) {
console.error("FAILED TO FIND RECOVERY CODE.", error.message);
return null;
}
},
findMany: async function (clause = {}) {
try {
const recoveryCodes = await prisma.recovery_codes.findMany({
where: clause,
});
return recoveryCodes;
} catch (error) {
console.error("FAILED TO FIND RECOVERY CODES.", error.message);
return null;
}
},
deleteMany: async function (clause = {}) {
try {
await prisma.recovery_codes.deleteMany({ where: clause });
return true;
} catch (error) {
console.error("FAILED TO DELETE RECOVERY CODES.", error.message);
return false;
}
},
hashesForUser: async function (userId = null) {
if (!userId) return [];
return (await this.findMany({ user_id: userId })).map(
(recovery) => recovery.code_hash
);
},
};
const PasswordResetToken = {
tablename: "password_reset_tokens",
resetExpiryMs: 600_000, // 10 minutes in ms;
writable: [],
calcExpiry: function () {
return new Date(Date.now() + this.resetExpiryMs);
},
create: async function (userId) {
try {
const passwordResetToken = await prisma.password_reset_tokens.create({
data: { user_id: userId, token: v4(), expiresAt: this.calcExpiry() },
});
return { passwordResetToken, error: null };
} catch (error) {
console.error("FAILED TO CREATE PASSWORD RESET TOKEN.", error.message);
return { passwordResetToken: null, error: error.message };
}
},
findUnique: async function (clause = {}) {
try {
const passwordResetToken = await prisma.password_reset_tokens.findUnique({
where: clause,
});
return passwordResetToken;
} catch (error) {
console.error("FAILED TO FIND PASSWORD RESET TOKEN.", error.message);
return null;
}
},
deleteMany: async function (clause = {}) {
try {
await prisma.password_reset_tokens.deleteMany({ where: clause });
return true;
} catch (error) {
console.error("FAILED TO DELETE PASSWORD RESET TOKEN.", error.message);
return false;
}
},
};
module.exports = {
RecoveryCode,
PasswordResetToken,
};