v3: added password change/reset feature
This commit is contained in:
@@ -22,8 +22,17 @@ import {
|
||||
updateUser,
|
||||
} from "../services/user-service.ts";
|
||||
import { redeemInvite, validateInvite } from "../services/invite-service.ts";
|
||||
import {
|
||||
requestPasswordReset,
|
||||
resetPassword,
|
||||
} from "../services/password-reset-service.ts";
|
||||
import { isEmailEnabled, sendEmail } from "../services/email-service.ts";
|
||||
import { FROM_EMAIL, OG_SITE_NAME, WELCOME_EMAIL_BODY } from "../config.ts";
|
||||
import {
|
||||
FROM_EMAIL,
|
||||
OG_SITE_NAME,
|
||||
VALIDATION,
|
||||
WELCOME_EMAIL_BODY,
|
||||
} from "../config.ts";
|
||||
import { marked } from "marked";
|
||||
import { broadcastUserUpdated } from "../services/ws-service.ts";
|
||||
import {
|
||||
@@ -163,6 +172,74 @@ router.get("/me", authMiddleware, (ctx: AuthContext) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Request a password reset email (unauthenticated; always returns 200)
|
||||
router.post("/request-password-reset", async (ctx) => {
|
||||
const body = await ctx.request.body.json();
|
||||
const email = typeof body?.email === "string" ? body.email.trim() : "";
|
||||
if (email) {
|
||||
await requestPasswordReset(email).catch((err) =>
|
||||
console.error("[request-password-reset]", err)
|
||||
);
|
||||
}
|
||||
ctx.response.body = { success: true };
|
||||
});
|
||||
|
||||
// Consume a reset token and set a new password (unauthenticated)
|
||||
router.post("/reset-password", async (ctx) => {
|
||||
const body = await ctx.request.body.json();
|
||||
const { token, newPassword } = (body ?? {}) as Record<string, unknown>;
|
||||
if (typeof token !== "string" || typeof newPassword !== "string") {
|
||||
throw new APIException(
|
||||
APIErrorCode.VALIDATION_ERROR,
|
||||
400,
|
||||
"Invalid request",
|
||||
);
|
||||
}
|
||||
await resetPassword(token, newPassword);
|
||||
ctx.response.body = { success: true };
|
||||
});
|
||||
|
||||
// Change current user's password (requires current password verification)
|
||||
router.post("/me/change-password", authMiddleware, async (ctx: AuthContext) => {
|
||||
const body = await ctx.request.body.json();
|
||||
const { currentPassword, newPassword } = (body ?? {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
if (typeof currentPassword !== "string" || typeof newPassword !== "string") {
|
||||
throw new APIException(
|
||||
APIErrorCode.VALIDATION_ERROR,
|
||||
400,
|
||||
"Invalid request",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
newPassword.length < VALIDATION.PASSWORD_MIN ||
|
||||
newPassword.length > VALIDATION.PASSWORD_MAX
|
||||
) {
|
||||
throw new APIException(
|
||||
APIErrorCode.VALIDATION_ERROR,
|
||||
400,
|
||||
`Password must be ${VALIDATION.PASSWORD_MIN}–${VALIDATION.PASSWORD_MAX} characters`,
|
||||
);
|
||||
}
|
||||
|
||||
const user = getUserById(ctx.state.user.userId);
|
||||
const valid = await verifyPassword(currentPassword, user.passwordHash);
|
||||
if (!valid) {
|
||||
throw new APIException(
|
||||
APIErrorCode.VALIDATION_ERROR,
|
||||
401,
|
||||
"Current password is incorrect",
|
||||
);
|
||||
}
|
||||
|
||||
await updateUser(ctx.state.user.userId, { password: newPassword });
|
||||
ctx.response.body = { success: true };
|
||||
});
|
||||
|
||||
// Update current user profile (description, etc.)
|
||||
router.patch("/me", authMiddleware, async (ctx: AuthContext) => {
|
||||
const body = await ctx.request.body.json();
|
||||
|
||||
Reference in New Issue
Block a user