Files
crates/crates/routes/login.tsx
T
2025-04-29 18:02:15 +10:00

145 lines
3.9 KiB
TypeScript

import { h } from "preact";
import { Handlers, PageProps } from "$fresh/server.ts";
import { getCookies, setCookie } from "$std/http/cookie.ts";
import { z } from "npm:zod";
import Swirl from "../components/swirl.tsx";
import { sessions } from "../api/auth.middleware.ts";
import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts";
import { createPg } from "../api/pg.ts";
export const handler: Handlers = {
async GET(req, ctx) {
const cookies = getCookies(req.headers);
const sessionId = cookies.session;
// If user is already logged in, redirect to home
if (sessionId && sessions.has(sessionId)) {
return new Response("", {
status: 307,
headers: { Location: "/" },
});
}
return ctx.render();
},
async POST(req, ctx) {
const formData = await req.formData();
// Define login schema with Zod
const LoginSchema = z.object({
email: z.string().email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters")
});
// Validate the form data
const result = LoginSchema.safeParse({
email: formData.get("email"),
password: formData.get("password")
});
if (!result.success) {
// Format validation errors
const fieldErrors = result.error.flatten().fieldErrors;
const firstError =
fieldErrors.email?.[0] ||
fieldErrors.password?.[0] ||
"Invalid form data";
return ctx.render({ error: firstError });
}
const { email, password } = result.data;
try {
// Create pg instance for database operations
const pg = createPg(console);
// Find user by email
const user = await pg.userFindEmail(email);
if (!user) {
return ctx.render({ error: "Invalid credentials" });
}
// Verify password
const match = await bcrypt.compare(password, user.password);
if (!match) {
return ctx.render({ error: "Invalid credentials" });
}
// Create session
const sessionId = crypto.randomUUID();
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7); // Session expires in 7 days
sessions.set(sessionId, {
userId: user.id,
expiresAt
});
// Set cookie and redirect
const headers = new Headers({ Location: "/" });
setCookie(headers, {
name: "session",
value: sessionId,
expires: expiresAt,
path: "/",
httpOnly: true,
secure: true,
sameSite: "Lax"
});
return new Response(null, {
status: 303,
headers
});
} catch (error) {
console.error("Login error:", error);
return ctx.render({ error: "An error occurred during login" });
}
}
};
function Login({ error }: { error?: string }) {
const errorEl = error
? <div class="alt-text">{error}</div>
: null;
return (
<div class="login">
{errorEl}
<form method="POST" action="/login">
<div class="item">
<label for="email">email</label>
<input type="email" name="email" id="email" required />
</div>
<div class="item">
<label for="password">password</label>
<input type="password" name="password" id="password" required />
</div>
<div class="item">
<input type="submit" value="login" />
</div>
</form>
<a href="/register" class="button">create account</a>
</div>
);
}
export default function LoginPage(props: PageProps) {
const { error } = props.data || {};
return (
<div class="landing">
<Login error={error} />
<Swirl />
<div class="words">
<p>record importing service</p>
<p>we bundle orders together from thousands of online stores to reduce the cost of shipping vinyl</p>
<p>then we get together and play them</p>
</div>
</div>
);
}