Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/Savy011/acm-website-tanstack.
Something went wrong. Try again.
8.7 kB · 286 lines
TypeScript
at savy/macros
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287import { render, toPlainText } from "@react-email/components";import Elysia from "elysia";import nodemailer from "nodemailer";import z from "zod";
import VerifyEmail from "$emails/verify";import { env } from "$env/server";import { API_URL } from "$lib/constants/api";import { BRANCH_NAMES } from "$lib/constants/details";
import { isErrorResponse } from "./helpers";import { APIUserSchema, UserSchema } from "./schemas";
const LoginResponseSchema = z.object({ jwtToken: z.string(), user: UserSchema,});
const APILoginResponseSchema = z.object({ jwtToken: z.string(), user: APIUserSchema,});
export const api = new Elysia({ prefix: "/auth" }) .model({ error: z.object({ error: z.string(), message: z.string(), timestamp: z.iso.datetime(), }), }) .post( "/login", async ({ body, status }) => { const res = await fetch(API_URL.V1.USER.LOGIN, { method: "POST", body: JSON.stringify(body), headers: { "Content-Type": "application/json", }, });
const response = await res.json();
if (isErrorResponse(response)) { switch (response.code) { case "Not Found": return status(404, { error: response.code, message: response.message, timestamp: new Date().toISOString(), }); case "Unauthorized": return status(401, { error: response.code, message: response.message, timestamp: new Date().toISOString(), }); default: return status(500, { error: "Internal Server Error", message: "An unexpected error occurred while handling request", timestamp: new Date().toISOString(), }); } }
const validatedRes = APILoginResponseSchema.parse(response);
return { jwtToken: validatedRes.jwtToken, user: { ...validatedRes.user, profile: validatedRes.user.dp, experience: validatedRes.user.xp, }, }; }, { body: z.object({ email: z.email("you need to enter a email to login"), password: z.string("you need to enter a password to login"), }), response: { 200: LoginResponseSchema, 401: "error", 404: "error", 500: "error", }, detail: { summary: "User login with email and password", description: `Authenticates a user with their email and password credentials. On successful authentication, returns a JWT token for subsequent authenticated requests along with the user's profile information including their designation, batch, experience points, and verification status.
**Response Codes:**- **200**: Authentication successful - returns JWT token and user profile- **401**: Invalid credentials - email or password is incorrect- **404**: User not found - no account exists with the provided email- **500**: Server error - an unexpected error occurred during authentication`, tags: ["Authentication"], }, }, ) .get( "/verify-email/:token", async ({ params, status }) => { const token = params.token;
const url = `${API_URL.V1.USER.VERIFY}?${new URLSearchParams({ token })}`;
const res = await fetch(url, { method: "GET" });
if (!res.ok) { const response = await res.json();
if (isErrorResponse(response)) { console.log(response); switch (response.code) { case "Not Found": return status(404, { error: "Not Found", message: "The provided verification token was not found or has expired", timestamp: new Date().toISOString(), }); case "Bad Request": return status(400, { error: "Bad Request", message: "The Email verification token is not of the expected type.", timestamp: new Date().toISOString(), }); default: return status(500, { error: "Internal Server Error", message: "An unexpected error occurred while handling request", timestamp: new Date().toISOString(), }); } } }
// TODO: It's more ideal to redirect the user to confirmation page, instead of showing a bland json response. return { message: "Your Email was sucessfully verified." }; }, { response: { 200: z.object({ message: z.literal("Your Email was sucessfully verified."), }), 400: "error", 404: "error", 500: "error", }, detail: { summary: "Verify user email", description: `Verifies a user's email address using a one-time verification token. The token is sent to the user's registered email address during account registration.
**Response Codes:**- **200**: Email successfully verified- **400**: Invalid or malformed token- **404**: Token not found or expired- **500**: Server error`, tags: ["Authentication"], }, }, ) .post( "/register", async ({ body, status }) => { const res = await fetch(API_URL.V1.USER.REGISTER, { method: "POST", body: JSON.stringify({ name: body.name, sid: body.sid, branch: body.branch, email: body.email, password: body.password, }), headers: { "Content-Type": "application/json", }, });
const response = await res.json();
if (isErrorResponse(response)) { switch (response.code) { case "Bad Request": return status(400, { error: "Bad Request", message: response.message, timestamp: new Date().toISOString(), }); default: return status(500, { error: "Internal Server Error", message: "An unexpected error occurred while handling request", timestamp: new Date().toISOString(), }); } }
const emailToken = response.emailToken;
const transporter = nodemailer.createTransport({ host: env.SMTP_HOST, port: env.SMTP_PORT, auth: { user: env.SMTP_EMAIL, pass: env.SMTP_PASS, }, });
const html = await render( VerifyEmail({ name: body.name, token: emailToken }), ); const text = toPlainText(html);
await transporter.sendMail({ from: `PEC ACM <${env.SMTP_EMAIL}>`, to: body.email, subject: "New Account Verification", text, html, });
return { message: "Account created! Please check your email to verify your account", }; }, { body: z .object({ name: z .string() .trim() .min(1, "Name is required") .max(100, "Name too long"), email: z.email().endsWith("@pec.edu.in", { error: "We only accept '@pec.edu.in' emails.", }), branch: z.enum(BRANCH_NAMES), sid: z.int().min(10000000).max(99999999), password: z .string() .regex( /((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\W]).)/gm, "The password needs atleast 1 lowercase character, 1 uppercase character, 1 number and 1 special character", ) .min(8) .max(20, "You sure you can remember that?"), confirmPassword: z.string().min(1, "Please confirm your password"), }) .refine(data => data.password === data.confirmPassword, { error: "Password and confirm password are not same.", path: ["confirmPassword"], }), response: { 200: z.object({ message: z.literal( "Account created! Please check your email to verify your account", ), }), 400: "error", 500: "error", }, detail: { summary: "Register a new user", description: `Creates a new user account and sends an email verification link. Only '@pec.edu.in' email addresses are accepted. The user must verify their email before logging in. **Response Codes:**- **200**: Account created and verification email sent- **400**: Validation error or duplicate account- **500**: Server error`, tags: ["Authentication"], }, }, );