From 4b93f0c7f724e96667245bad8cd0658101fbdec6 Mon Sep 17 00:00:00 2001 From: Matteo Rosati Date: Mon, 26 Jan 2026 00:36:05 +0100 Subject: [PATCH] basic register --- login.html | 23 ++-- .../migration.sql | 3 +- prisma/schema.prisma | 8 +- register.html | 37 ++++-- server.ts | 108 ++++++++++++++++-- src/auth/messages.ts | 11 ++ src/login.ts | 17 ++- src/register.ts | 25 ++++ src/services/database-service.ts | 2 +- src/types/types.ts | 10 ++ src/utilities/email.ts | 4 + src/utilities/password.ts | 13 +++ 12 files changed, 217 insertions(+), 44 deletions(-) rename prisma/migrations/{20260125085125_init => 20260125230748_init}/migration.sql (76%) create mode 100644 src/auth/messages.ts create mode 100644 src/register.ts create mode 100644 src/utilities/email.ts create mode 100644 src/utilities/password.ts diff --git a/login.html b/login.html index ff3d510..039b7ed 100644 --- a/login.html +++ b/login.html @@ -3,17 +3,24 @@

LOGIN

- Game -
+ +

+ Game +

+ +
-
+ + (required)
-
- -
-
+ + (required)
+ + + +

Register? -

+

diff --git a/prisma/migrations/20260125085125_init/migration.sql b/prisma/migrations/20260125230748_init/migration.sql similarity index 76% rename from prisma/migrations/20260125085125_init/migration.sql rename to prisma/migrations/20260125230748_init/migration.sql index b6a8924..ed6acf8 100644 --- a/prisma/migrations/20260125085125_init/migration.sql +++ b/prisma/migrations/20260125230748_init/migration.sql @@ -2,7 +2,8 @@ CREATE TABLE "User" ( "id" SERIAL NOT NULL, "email" TEXT NOT NULL, - "name" TEXT, + "password" TEXT NOT NULL, + "first_login" BOOLEAN NOT NULL, CONSTRAINT "User_pkey" PRIMARY KEY ("id") ); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 44bd9a8..8b92553 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,8 +8,8 @@ datasource db { } model User { - id Int @id @default(autoincrement()) - email String @unique - name String? - password String + id Int @id @default(autoincrement()) + email String @unique + password String + first_login Boolean } diff --git a/register.html b/register.html index 7a595bc..882a243 100644 --- a/register.html +++ b/register.html @@ -3,18 +3,31 @@

REGISTER

- Game -
- -
- -
- -
-
- Login? -
- +

+ Game +

+ +
+ + + (required)
+ + + (required, min 8 chars)
+ +
+ +

+ Login? +

+ + diff --git a/server.ts b/server.ts index 1885679..6001e51 100644 --- a/server.ts +++ b/server.ts @@ -1,31 +1,115 @@ import { Hono } from "hono"; +import { cors } from "hono/cors"; import { upgradeWebSocket, websocket } from "hono/bun"; import { DatabaseService } from "@/services/database-service"; +import { RegisterRequest, RegisterResponse } from "@/types/types"; +import { MESSAGES } from "@/auth/messages"; +import { validateEmail } from "@/utilities/email"; +import { Prisma } from "@/orm/generated/prisma/client"; +import { hashPassword } from "@/utilities/password"; const app = new Hono(); +app.use( + "/api/v1/*", + cors({ + origin: "*", + allowHeaders: ["X-Custom-Header", "Upgrade-Insecure-Requests"], + allowMethods: ["POST", "GET", "OPTIONS"], + exposeHeaders: ["Content-Length", "X-Kuma-Revision"], + maxAge: 600, + credentials: true, + }), +); + app.get("/", async (c) => { - const database = new DatabaseService(); - await database.getClient().user.create({ - data: { - email: "rosati5.matteo@gmail.com", - name: "Matteo", - }, - }); - - const users = await database.getClient().user.findMany(); - return c.json({ message: "ok", - users: users, }); }); +app.post("/api/v1/register", async (c) => { + let body: RegisterRequest | undefined; + let errors = false; + let messages: Array = []; + + // Get the request body and handle malformed payload + try { + body = (await c.req.json()) as RegisterRequest; + } catch (error) { + console.error(`Received invalid payload: ${error}`); + return c.json({ + status: "error", + messages: [MESSAGES.INVALID_REQUEST], + } as RegisterResponse); + } + + // ////////////////// + // Request validation + if (!body.email) { + errors = true; + messages.push(MESSAGES.MISSING_EMAIL); + } + + if (!validateEmail(body.email)) { + errors = true; + messages.push(MESSAGES.INVALID_EMAIL); + } + + if (!body.password) { + errors = true; + messages.push(MESSAGES.MISSING_PASSWORD); + } + // End: Request validation + // /////////////////////// + + if (errors) { + return c.json({ + status: "error", + messages: messages, + } as RegisterResponse); + } + + // Database + const database = new DatabaseService(); + + try { + // Sala la password + body.password = await hashPassword(body.password); + + await database.getClient().user.create({ + data: { + ...body, + first_login: true, + }, + }); + } catch (e) { + if ( + e instanceof Prisma.PrismaClientKnownRequestError && + e.code === "P2002" + ) { + return c.json({ + status: "error", + messages: [MESSAGES.USER_ALREADY_EXISTS], + } as RegisterResponse); + } + + return c.json({ + status: "error", + messages: [MESSAGES.UNKNOWN_DATABASE_ERROR], + } as RegisterResponse); + } + + return c.json({ + status: "success", + } as RegisterResponse); +}); + app.get( "/ws", upgradeWebSocket((c) => { return { - onOpen(event, ws) { + onOpen(e, ws) { console.log("Server: Connection opened"); ws.send("Hello!"); }, diff --git a/src/auth/messages.ts b/src/auth/messages.ts new file mode 100644 index 0000000..39f58e0 --- /dev/null +++ b/src/auth/messages.ts @@ -0,0 +1,11 @@ +export const MESSAGES = { + MISSING_EMAIL: "The e-mail address is missing", + INVALID_EMAIL: "The e-mail address is invalid", + MISSING_PASSWORD: "The password is missing", + WRONG_PASSWORD: "Wrong password", + USER_NOT_FOUND: "User not found", + USER_CREATED: "The user has been created", + USER_ALREADY_EXISTS: "This user already exists", + INVALID_REQUEST: "Invalid request", + UNKNOWN_DATABASE_ERROR: "Il database ha dato un errore sconosciuto", +}; diff --git a/src/login.ts b/src/login.ts index 4fcd0fb..08e8a24 100644 --- a/src/login.ts +++ b/src/login.ts @@ -1,14 +1,19 @@ // STYLES -// import "modern-normalize/modern-normalize.css"; -// import "@fontsource-variable/jetbrains-mono"; +import "modern-normalize/modern-normalize.css"; +import "@fontsource-variable/jetbrains-mono"; import "@/main.css"; -// import "@maptiler/sdk/dist/maptiler-sdk.css"; // LIBRARIES -// import $ from "jquery"; +import $ from "jquery"; // import { FilterSpecification, Map, config } from "@maptiler/sdk"; // import { createIcons, Locate, LocateFixed } from "lucide"; -// import area from "@turf/area"; -// import { polygon } from "@turf/helpers"; console.log("login"); + +$("#login").on("submit", (e) => { + e.preventDefault(); + const email = $("#email").val(); + const password = $("#password").val(); + + console.log(email, password); +}); diff --git a/src/register.ts b/src/register.ts new file mode 100644 index 0000000..06e7d97 --- /dev/null +++ b/src/register.ts @@ -0,0 +1,25 @@ +// STYLES +import "modern-normalize/modern-normalize.css"; +import "@fontsource-variable/jetbrains-mono"; +import "@/main.css"; + +// LIBRARIES +import $ from "jquery"; + +const API_SERVER = import.meta.env.VITE_API_SERVER!; + +$("#register").on("submit", async (e) => { + e.preventDefault(); + const email = $("#email"); + const password = $("#password"); + + const response = await fetch(`${API_SERVER}/api/v1/register`, { + method: "POST", + body: JSON.stringify({ + email: email.val(), + password: password.val(), + }), + }); + + console.log(await response.json()); +}); diff --git a/src/services/database-service.ts b/src/services/database-service.ts index af763d8..69ca7ee 100644 --- a/src/services/database-service.ts +++ b/src/services/database-service.ts @@ -1,5 +1,5 @@ import { PrismaPg } from "@prisma/adapter-pg"; -import { PrismaClient } from "@/orm/generated/prisma"; +import { PrismaClient } from "@/orm/generated/prisma/client"; export class DatabaseService { private prisma: PrismaClient; diff --git a/src/types/types.ts b/src/types/types.ts index 1b99c22..f7a407a 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -92,3 +92,13 @@ export interface SelectedFeature { id: number; area: number; } + +export interface RegisterRequest { + email: string; + password: string; +} + +export interface RegisterResponse { + status: "success" | "error"; + messages: Array | undefined; +} diff --git a/src/utilities/email.ts b/src/utilities/email.ts new file mode 100644 index 0000000..3b3f375 --- /dev/null +++ b/src/utilities/email.ts @@ -0,0 +1,4 @@ +export const validateEmail = (email: string): boolean => { + const emailRegex: RegExp = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + return emailRegex.test(email); +}; diff --git a/src/utilities/password.ts b/src/utilities/password.ts new file mode 100644 index 0000000..753e5ad --- /dev/null +++ b/src/utilities/password.ts @@ -0,0 +1,13 @@ +export const hashPassword = async (password: string): Promise => { + const PASSWORD_SALT = Bun.env.PASSWORD_SALT!; + const saltedPassword = PASSWORD_SALT + password; + const encoder = new TextEncoder(); + const data = encoder.encode(saltedPassword); + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + return hashHex; +};