first commit
Some checks failed
Code quality checks / biome (push) Failing after 11s

This commit is contained in:
creations 2025-06-10 13:42:39 -04:00
commit 421043c9b5
Signed by: creations
GPG key ID: 8F553AA4320FC711
67 changed files with 3455 additions and 0 deletions

View file

@ -0,0 +1,215 @@
import { echo } from "@atums/echo";
import { sessionManager } from "#lib/auth";
import { cassandra } from "#lib/database";
import { isValidPassword } from "#lib/validation";
import type {
ExtendedRequest,
RouteDef,
UpdatePasswordRequest,
UpdatePasswordResponse,
UserRow,
} from "#types/server";
const routeDef: RouteDef = {
method: ["PUT", "PATCH"],
accepts: "application/json",
returns: "application/json",
needsBody: "json",
};
async function handler(
request: ExtendedRequest,
requestBody: unknown,
): Promise<Response> {
try {
const session = await sessionManager.getSession(request);
if (!session) {
const response: UpdatePasswordResponse = {
code: 401,
success: false,
error: "Not authenticated",
};
return Response.json(response, { status: 401 });
}
const { currentPassword, newPassword, logoutAllSessions } =
requestBody as UpdatePasswordRequest;
if (!currentPassword || !newPassword) {
const response: UpdatePasswordResponse = {
code: 400,
success: false,
error: "Both currentPassword and newPassword are required",
};
return Response.json(response, { status: 400 });
}
const passwordValidation = isValidPassword(newPassword);
if (!passwordValidation.valid) {
const response: UpdatePasswordResponse = {
code: 400,
success: false,
error: passwordValidation.error || "Invalid new password",
};
return Response.json(response, { status: 400 });
}
if (currentPassword === newPassword) {
const response: UpdatePasswordResponse = {
code: 400,
success: false,
error: "New password must be different from current password",
};
return Response.json(response, { status: 400 });
}
const userQuery = `
SELECT id, username, email, password, is_verified, created_at, updated_at
FROM users WHERE id = ? LIMIT 1
`;
const userResult = (await cassandra.execute(userQuery, [session.id])) as {
rows: UserRow[];
};
if (!userResult?.rows || userResult.rows.length === 0) {
await sessionManager.invalidateSession(request);
const response: UpdatePasswordResponse = {
code: 404,
success: false,
error: "User not found",
};
return Response.json(response, { status: 404 });
}
const user = userResult.rows[0];
if (!user) {
const response: UpdatePasswordResponse = {
code: 404,
success: false,
error: "User not found",
};
return Response.json(response, { status: 404 });
}
const isCurrentPasswordValid = await Bun.password.verify(
currentPassword,
user.password,
);
if (!isCurrentPasswordValid) {
const response: UpdatePasswordResponse = {
code: 401,
success: false,
error: "Current password is incorrect",
};
return Response.json(response, { status: 401 });
}
const hashedNewPassword = await Bun.password.hash(newPassword, {
algorithm: "argon2id",
memoryCost: 4096,
timeCost: 3,
});
const updateQuery = `
UPDATE users
SET password = ?, updated_at = ?
WHERE id = ?
`;
await cassandra.execute(updateQuery, [
hashedNewPassword,
new Date(),
session.id,
]);
if (logoutAllSessions === true) {
const invalidatedCount =
await sessionManager.invalidateAllSessionsForUser(session.id);
const response: UpdatePasswordResponse = {
code: 200,
success: true,
message: `Password updated successfully. Logged out from ${invalidatedCount} sessions.`,
loggedOutSessions: invalidatedCount,
};
return Response.json(response, {
status: 200,
headers: {
"Content-Type": "application/json",
"Set-Cookie": "session=; Path=/; Max-Age=0; HttpOnly",
},
});
}
const allSessions = await sessionManager.getActiveSessionsForUser(
session.id,
);
const currentToken = request.headers
.get("Cookie")
?.match(/session=([^;]+)/)?.[1];
let invalidatedCount = 0;
if (currentToken) {
for (const token of allSessions) {
if (token !== currentToken) {
await sessionManager.invalidateSessionByToken(token);
invalidatedCount++;
}
}
}
const userAgent = request.headers.get("User-Agent") || "Unknown";
const updatedSessionPayload = {
id: user.id,
username: user.username,
email: user.email,
isVerified: user.is_verified,
displayName: user.display_name,
createdAt: user.created_at.toISOString(),
updatedAt: new Date().toISOString(),
};
const sessionCookie = await sessionManager.updateSession(
request,
updatedSessionPayload,
userAgent,
);
const response: UpdatePasswordResponse = {
code: 200,
success: true,
message:
invalidatedCount > 0
? `Password updated successfully. Logged out from ${invalidatedCount} other sessions.`
: "Password updated successfully.",
loggedOutSessions: invalidatedCount,
};
return Response.json(response, {
status: 200,
headers: {
"Content-Type": "application/json",
"Set-Cookie": sessionCookie,
},
});
} catch (error) {
echo.error({
message: "Error updating user password",
error,
});
const response: UpdatePasswordResponse = {
code: 500,
success: false,
error: "Internal server error",
};
return Response.json(response, { status: 500 });
}
}
export { handler, routeDef };