update some login things
This commit is contained in:
parent
a52be45907
commit
b40c1db189
7 changed files with 333 additions and 37 deletions
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
37
public/js/auth/login.js
Normal 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.";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Reference in a new issue