From e00a589ae6d2846a1eecf73a86795d96072dec33 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Tue, 23 Sep 2025 23:15:44 +0200 Subject: [PATCH] Simple actual auth --- package-lock.json | 160 ++++++++++++++++++++++++++++++++++++++ package.json | 5 +- src/app/api/auth/route.ts | 7 +- src/app/api/db.ts | 32 ++++++++ src/app/api/user/route.ts | 57 ++++++++++++++ src/test/user.js | 12 +++ 6 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 src/app/api/db.ts create mode 100644 src/app/api/user/route.ts create mode 100644 src/test/user.js diff --git a/package-lock.json b/package-lock.json index 63bc46a..faec7c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "lexical": "^0.34.0", "lucide-react": "^0.541.0", "next": "15.5.0", + "pg": "^8.16.3", "react": "19.1.0", "react-dom": "19.1.0", "react-markdown": "^10.1.0", @@ -37,6 +38,7 @@ "devDependencies": { "@tailwindcss/postcss": "^4", "@types/node": "^20.19.11", + "@types/pg": "^8.15.5", "@types/react": "^19", "@types/react-dom": "^19", "tailwindcss": "^4", @@ -2013,6 +2015,18 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pg": { + "version": "8.15.5", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz", + "integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/react": { "version": "19.1.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", @@ -4133,6 +4147,95 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4168,6 +4271,45 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4483,6 +4625,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -4804,6 +4955,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", diff --git a/package.json b/package.json index 2005d5b..c515807 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,9 @@ "dev": "next dev", "build": "next build", "start": "next start", + "test": "echo 'Run a specific test with npm test:'", "test:auth": "node src/test/auth.js", - "test": "echo 'Run a specific test with npm test:'" + "test:user": "node src/test/user.js" }, "dependencies": { "@lexical/react": "^0.34.0", @@ -30,6 +31,7 @@ "lexical": "^0.34.0", "lucide-react": "^0.541.0", "next": "15.5.0", + "pg": "^8.16.3", "react": "19.1.0", "react-dom": "19.1.0", "react-markdown": "^10.1.0", @@ -39,6 +41,7 @@ "devDependencies": { "@tailwindcss/postcss": "^4", "@types/node": "^20.19.11", + "@types/pg": "^8.15.5", "@types/react": "^19", "@types/react-dom": "^19", "tailwindcss": "^4", diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts index cd2ddc9..901ae3d 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -4,6 +4,7 @@ import { } from 'http-status-codes'; import { narrow } from "@/types/typeUtils"; import { CLIENT_AUTH_TOKENS, SERVER_AUTH_TOKENS } from "./auth"; +import { initDb } from "../db"; type Intents = 'server' | 'client'; @@ -57,7 +58,7 @@ export async function POST(req: NextRequest) { switch (narrowIntents(intents[0])) { case 'server': SERVER_AUTH_TOKENS[token] = { - user_id, // placeholder + user_id, server_ip: String(server_ip), }; return NextResponse.json({ message: "ok", token }); @@ -67,4 +68,6 @@ export async function POST(req: NextRequest) { default: return NextResponse.json({ message: 'Invalid intentions' }, { status: StatusCodes.BAD_REQUEST }); } -} \ No newline at end of file +} + +initDb(); \ No newline at end of file diff --git a/src/app/api/db.ts b/src/app/api/db.ts new file mode 100644 index 0000000..69d603c --- /dev/null +++ b/src/app/api/db.ts @@ -0,0 +1,32 @@ +import { Pool } from "pg"; + +const pool = new Pool({ + user: "klestiselimaj", + host: "localhost", + database: "postgres", + password: "", + port: 5432, +}); + +const createUsersTableQuery = ` + CREATE TABLE IF NOT EXISTS users ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(32) UNIQUE NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + created_at TIMESTAMP DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP DEFAULT NOW() NOT NULL + ); +`; + +export async function initDb() { + try { + await pool.query(createUsersTableQuery); + console.log("Users table is ready"); + } catch (err) { + console.error("Error creating users table:", err); + throw err; + } +} + +export default pool; diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts new file mode 100644 index 0000000..2ad60b6 --- /dev/null +++ b/src/app/api/user/route.ts @@ -0,0 +1,57 @@ +import { NextRequest, NextResponse } from "next/server"; +import pool from "../db"; +import { StatusCodes } from "http-status-codes"; +import { CLIENT_AUTH_TOKENS } from "../auth/auth"; + +/* +Input: + { + "name": "", + "password": "" + } +Output: + { + "message": "ok", + "token": "", + } +*/ +export async function POST(req: NextRequest) { + let { name, username, email, password } = await req.json(); + + const user_ = await pool.query( + "SELECT id, username, email FROM users WHERE username = $1;", + [username] + ); + + if (user_.rows.length > 0) { + return NextResponse.json({ message: "Username already used" }, { status: StatusCodes.CONFLICT }); + } + + const user_email = await pool.query( + "SELECT id, username, email FROM users WHERE email = $1;", + [email] + ); + + if (user_email.rows.length > 0) { + return NextResponse.json({ message: "Email already used" }, { status: StatusCodes.CONFLICT }); + } + + // TODO: Hash the password + const result = await pool.query("INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3) RETURNING id;", [username, email, password]); + const token = crypto.randomUUID(); + + CLIENT_AUTH_TOKENS[token] = result.rows[0].id; + + return NextResponse.json({ message: "ok", token, user_id: result.rows[0].id }); +} + +export async function DELETE(req: NextRequest) { + let { username } = await req.json(); + + await pool.query( + "DELETE FROM users WHERE username = $1;", + [username] + ); + + return NextResponse.json({ message: "ok" }); +} diff --git a/src/test/user.js b/src/test/user.js new file mode 100644 index 0000000..2bdf13f --- /dev/null +++ b/src/test/user.js @@ -0,0 +1,12 @@ +import axios from "axios"; + +console.log((await axios.delete("http://localhost:3000/api/user", { + data: { username: "leo" }, +})).data); + +console.log((await axios.post('http://localhost:3000/api/user', { + username: 'leo', + name: 'Leo', + email: '', + password: 'Idk123', +})).data);