update some login things

This commit is contained in:
creations 2025-03-18 22:55:22 -04:00
parent a52be45907
commit b40c1db189
Signed by: creations
GPG key ID: 8F553AA4320FC711
7 changed files with 333 additions and 37 deletions

View file

@ -70,6 +70,10 @@ export function isValidUsername(username: string): {
valid: boolean; valid: boolean;
error?: string; error?: string;
} { } {
if (!username) {
return { valid: false, error: "" };
}
if (username.length < userNameRestrictions.length.min) { if (username.length < userNameRestrictions.length.min) {
return { valid: false, error: "Username is too short" }; return { valid: false, error: "Username is too short" };
} }
@ -89,6 +93,10 @@ export function isValidPassword(password: string): {
valid: boolean; valid: boolean;
error?: string; error?: string;
} { } {
if (!password) {
return { valid: false, error: "" };
}
if (password.length < passwordRestrictions.length.min) { if (password.length < passwordRestrictions.length.min) {
return { return {
valid: false, valid: false,
@ -117,6 +125,10 @@ export function isValidEmail(email: string): {
valid: boolean; valid: boolean;
error?: string; error?: string;
} { } {
if (!email) {
return { valid: false, error: "" };
}
if (!emailRestrictions.regex.test(email)) { if (!emailRestrictions.regex.test(email)) {
return { valid: false, error: "Invalid email address" }; return { valid: false, error: "Invalid email address" };
} }
@ -128,6 +140,10 @@ export function isValidInvite(invite: string): {
valid: boolean; valid: boolean;
error?: string; error?: string;
} { } {
if (!invite) {
return { valid: false, error: "" };
}
if (invite.length < inviteRestrictions.min) { if (invite.length < inviteRestrictions.min) {
return { return {
valid: false, valid: false,

View file

@ -9,13 +9,11 @@
.content { .content {
border: 1px solid var(--border); border: 1px solid var(--border);
background-color: var(--background-secondary); background-color: var(--background-secondary);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 2rem; padding: 2rem;
width: clamp(200px, 50%, 300px); width: clamp(200px, 50%, 300px);
} }
@ -25,3 +23,137 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.login-container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
min-height: 100vh;
background: linear-gradient(135deg, rgba(31 30 30 / 90%) 0%, rgba(45 45 45 / 90%) 100%);
}
.login-logo {
margin-bottom: 2rem;
text-align: center;
}
.login-logo h1 {
font-size: 2.5rem;
font-weight: bold;
color: var(--text);
margin: 0;
}
.login-logo p {
color: var(--text-secondary);
margin-top: 0.5rem;
}
.login-card {
width: 100%;
max-width: 400px;
border-radius: 8px;
box-shadow: var(--card-shadow);
background-color: var(--background-secondary);
overflow: hidden;
animation: fade-in 0.5s ease;
}
.login-header {
padding: 1.5rem;
background-color: rgba(0 0 0 / 10%);
text-align: center;
}
.login-header h2 {
margin: 0;
color: var(--text);
font-size: 1.5rem;
}
.login-form {
padding: 1.5rem;
}
.login-form form {
display: flex;
flex-direction: column;
width: 100%;
}
.login-register {
text-align: center;
margin-top: 1.5rem;
}
.form-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
}
.form-footer a {
color: var(--accent);
text-decoration: none;
font-size: 0.9rem;
}
.form-footer a:hover {
text-decoration: underline;
}
.form-footer label {
display: flex;
align-items: center;
gap: 0.25rem;
white-space: nowrap;
}
.login-form button {
margin-top: 1rem;
width: 100%;
}
.error-message {
color: var(--error);
background-color: rgb(237 66 69 / 10%);
border-left: 4px solid var(--error);
padding: 0.75rem;
margin-bottom: 1rem;
border-radius: 4px;
display: none;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.register-link {
color: #57f287;
text-decoration: none;
font-weight: bold;
}
.register-link:hover {
text-decoration: underline;
}
@media (width <= 480px) {
.login-card {
max-width: 100%;
}
.login-logo h1 {
font-size: 2rem;
}
}

View file

@ -1,40 +1,112 @@
[data-theme="dark"] { [data-theme="dark"] {
--background: rgb(31, 30, 30); --background: rgb(31 30 30);
--background-secondary: rgb(45, 45, 45); --background-secondary: rgb(45 45 45);
--border: rgb(31, 30, 30); --border: rgb(70 70 70);
--text: rgb(255, 255, 255); --text: rgb(255 255 255);
--text-secondary: rgb(255, 255, 255); --text-secondary: rgb(200 200 200);
--accent: rgb(88 101 242);
--accent-hover: rgb(71 82 196);
--error: rgb(237 66 69);
--success: rgb(87 242 135);
--shadow: rgb(0 0 0 / 20%);
--card-shadow: 0 2px 10px 0 rgb(0 0 0 / 20%);
--input-background: rgb(55 55 55);
} }
body { body {
font-family: "Ubuntu", sans-serif; font-family: Ubuntu, sans-serif;
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
font-size: 16px; font-size: 16px;
background-color: var(--background); background-color: var(--background);
color: var(--text);
} }
/* Fonts */ /* Fonts */
@font-face { @font-face {
font-family: "Ubuntu"; font-family: Ubuntu;
src: url("/public/assets/fonts/Ubuntu/Ubuntu-Regular.ttf") format("truetype"); src: url("/public/assets/fonts/Ubuntu/Ubuntu-Regular.ttf") format("truetype");
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: "Ubuntu Bold"; font-family: Ubuntu Bold;
src: url("/public/assets/fonts/Ubuntu/Ubuntu-Bold.ttf") format("truetype"); src: url("/public/assets/fonts/Ubuntu/Ubuntu-Bold.ttf") format("truetype");
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: "Fira Code"; font-family: Fira Code;
src: url("/public/assets/fonts/Fira_code/FiraCode-Regular.ttf") format("truetype"); src: url("/public/assets/fonts/Fira_code/FiraCode-Regular.ttf") format("truetype");
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
/* Utility classes */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
/* Form elements */
input, button, textarea, select {
font-family: inherit;
font-size: 1rem;
border-radius: 4px;
transition: all 0.2s ease;
}
button, .button {
cursor: pointer;
padding: 0.75rem 1.5rem;
border: none;
background-color: var(--accent);
color: white;
font-weight: bold;
border-radius: 4px;
transition: background-color 0.2s ease;
}
button:hover, .button:hover {
background-color: var(--accent-hover);
}
input, textarea, select {
padding: 0.75rem;
border: 1px solid var(--border);
background-color: var(--input-background);
color: var(--text);
width: 100%;
box-sizing: border-box;
}
input:focus, textarea:focus, select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px rgb(88 101 242 / 30%);
}
.form-group {
margin-bottom: 1.5rem;
width: 100%;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
color: var(--text-secondary);
}
/* Card style */
.card {
background-color: var(--background-secondary);
border-radius: 8px;
box-shadow: var(--card-shadow);
padding: 1.5rem;
overflow: hidden;
}

37
public/js/auth/login.js Normal file
View file

@ -0,0 +1,37 @@
const loginForm = document.getElementById("login-form");
const errorMessage = document.getElementById("error-message");
if (loginForm) {
loginForm.addEventListener("submit", async (e) => {
e.preventDefault();
const email = document.getElementById("email").value;
const password = document.getElementById("password").value;
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "same-origin",
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (data.success) {
window.location.href = "/";
} else {
errorMessage.style.display = "block";
errorMessage.textContent =
data.error ||
"Invalid email or password. Please try again.";
}
} catch (error) {
console.error("Login error:", error);
errorMessage.style.display = "block";
errorMessage.textContent = "An error occurred. Please try again.";
}
});
}

View file

@ -59,11 +59,16 @@ async function handler(
} }
const errors: string[] = []; const errors: string[] = [];
const validations: UserValidation[] = [ const validations: UserValidation[] = [
{ check: isValidUsername(username), field: "Username" }, username
{ check: isValidEmail(email), field: "Email" }, ? { check: isValidUsername(username), field: "Username" }
{ check: isValidPassword(password), field: "Password" }, : null,
]; email ? { check: isValidEmail(email), field: "Email" } : null,
password
? { check: isValidPassword(password), field: "Password" }
: null,
].filter(Boolean) as UserValidation[];
validations.forEach(({ check }: UserValidation): void => { validations.forEach(({ check }: UserValidation): void => {
if (!check.valid && check.error) { if (!check.valid && check.error) {
@ -71,6 +76,10 @@ async function handler(
} }
}); });
if (!username && !email) {
errors.push("Either a username or an email is required.");
}
if (errors.length > 0) { if (errors.length > 0) {
return Response.json( return Response.json(
{ {
@ -86,11 +95,11 @@ async function handler(
let user: User | null = null; let user: User | null = null;
try { try {
user = await reservation` [user] = await reservation`
SELECT * FROM users SELECT * FROM users
WHERE (username = ${username} OR email = ${email}) WHERE (username = ${username} OR email = ${email})
LIMIT 1; LIMIT 1;
`.then((rows: User[]): User | null => rows[0]); `;
if (!user) { if (!user) {
await bunPassword.verify("fake", await bunPassword.hash("fake")); await bunPassword.verify("fake", await bunPassword.hash("fake"));

View file

@ -8,10 +8,12 @@ const routeDef: RouteDef = {
}; };
async function handler(): Promise<Response> { async function handler(): Promise<Response> {
const instanceName: string =
(await getSetting("instance_name")) || "Unnamed Instance";
const ejsTemplateData: EjsTemplateData = { const ejsTemplateData: EjsTemplateData = {
title: "Hello, World!", title: `Login - ${instanceName}`,
instance_name: instance_name: instanceName,
(await getSetting("instance_name")) || "Unnamed Instance",
}; };
return await renderEjsTemplate("auth/login", ejsTemplateData); return await renderEjsTemplate("auth/login", ejsTemplateData);

View file

@ -1,28 +1,56 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<%- include("../global", { styles: ["auth/login"], scripts: [] }) %> <%- include("../global", { styles: ["auth/login"], scripts: ["auth/login"] }) %>
</head> </head>
<body> <body>
<div class="container"> <div class="login-container">
<div class="header"> <div class="login-logo">
<h1><%= instance_name %></h1> <h1>
<%= instance_name %>
</h1>
<p>Sign in to your account</p>
</div> </div>
<div class="content">
<div class="login-card">
<div class="login-header">
<h2>Welcome Back</h2>
</div>
<div class="login-form">
<div class="error-message" id="error-message">
Invalid email or password. Please try again.
</div>
<form id="login-form"> <form id="login-form">
<div class="form-group"> <div class="form-group">
<label for="email">Email</label> <label for="email">Email</label>
<input type="email" name="email" id="email" required> <input type="email" name="email" id="email" required placeholder="Enter your email">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password</label> <label for="password">Password</label>
<input type="password" name="password" id="password" required> <input type="password" name="password" id="password" required placeholder="Enter your password">
</div> </div>
<div class="form-group">
<div class="form-footer">
<label>
<input type="checkbox" name="remember" id="remember">Remember me
</label>
<a href="/auth/forgot-password">Forgot password?</a>
</div>
<button type="submit">Login</button> <button type="submit">Login</button>
</div>
</form> </form>
<div class="login-register">
<p>Don't have an account? <a href="/auth/register" class="register-link">Register</a></p>
</div>
</div>
</div> </div>
</div> </div>
</body> </body>
</html> </html>