diff options
author | Manurbhav | 2024-06-10 18:33:48 +0530 |
---|---|---|
committer | Manurbhav | 2024-06-10 18:33:48 +0530 |
commit | 0cbdfccf08f51ecc7ec4b0cc985e84df7a179fd1 (patch) | |
tree | 9f3d1c3b63589166039dec7a1c65a279322a276b | |
parent | df08439fe9a02def62de129b67782f2014418f0a (diff) | |
download | IotJS-Astro-0cbdfccf08f51ecc7ec4b0cc985e84df7a179fd1.tar.gz IotJS-Astro-0cbdfccf08f51ecc7ec4b0cc985e84df7a179fd1.tar.bz2 IotJS-Astro-0cbdfccf08f51ecc7ec4b0cc985e84df7a179fd1.zip |
Auth setup (partial)
Incomplete right now
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | package-lock.json | 12 | ||||
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | src/components/Dummy.astro | 23 | ||||
-rw-r--r-- | src/components/IconBar.astro | 2 | ||||
-rw-r--r-- | src/components/Login.astro | 15 | ||||
-rw-r--r-- | src/components/LoginBody.astro | 51 | ||||
-rw-r--r-- | src/components/ResultsBody.astro | 24 | ||||
-rw-r--r-- | src/components/SignupBody.astro | 44 | ||||
-rw-r--r-- | src/content/accordion/a1.md | 2 | ||||
-rw-r--r-- | src/env.d.ts | 6 | ||||
-rw-r--r-- | src/lib/auth.ts | 30 | ||||
-rw-r--r-- | src/lib/databaseUpdates.js | 39 | ||||
-rw-r--r-- | src/lib/db.ts | 27 | ||||
-rw-r--r-- | src/middleware.ts | 88 | ||||
-rw-r--r-- | src/pages/admin.astro | 46 | ||||
-rw-r--r-- | src/pages/api/login.ts | 71 | ||||
-rw-r--r-- | src/pages/api/logout.ts | 22 | ||||
-rw-r--r-- | src/pages/api/signup.ts | 85 | ||||
-rw-r--r-- | src/pages/login.astro | 22 | ||||
-rw-r--r-- | src/pages/signup.astro | 23 |
21 files changed, 565 insertions, 76 deletions
@@ -7,6 +7,9 @@ dist/ # dependencies node_modules/ +#database +mydatabase.db + # logs npm-debug.log* yarn-debug.log* diff --git a/package-lock.json b/package-lock.json index c3e3b77..57c8ee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,17 +14,17 @@ "@auth/core": "^0.18.6", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", - "@lucia-auth/adapter-sqlite": "^3.0.0", + "@lucia-auth/adapter-sqlite": "^3.0.1", "@popperjs/core": "^2.11.8", "@types/bootstrap": "^5.2.10", "@xexiu/astro-accordion": "^0.0.10", "astro": "^4.6.2", "astro-bootstrap": "^0.6.2", "auth-astro": "^4.1.1", - "better-sqlite3": "^9.2.2", + "better-sqlite3": "^11.0.0", "bootstrap": "^5.3.3", "lucia": "^3.2.0", - "oslo": "^1.0.3", + "oslo": "^1.2.0", "tailwindcss": "^3.4.3", "typescript": "^5.4.5" }, @@ -5109,9 +5109,9 @@ ] }, "node_modules/better-sqlite3": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz", - "integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.0.0.tgz", + "integrity": "sha512-1NnNhmT3EZTsKtofJlMox1jkMxdedILury74PwUbQBjWgo4tL4kf7uTAjU55mgQwjdzqakSTjkf+E1imrFwjnA==", "hasInstallScript": true, "dependencies": { "bindings": "^1.5.0", diff --git a/package.json b/package.json index 5b4acb3..f50f60f 100644 --- a/package.json +++ b/package.json @@ -16,17 +16,17 @@ "@auth/core": "^0.18.6", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", - "@lucia-auth/adapter-sqlite": "^3.0.0", + "@lucia-auth/adapter-sqlite": "^3.0.1", "@popperjs/core": "^2.11.8", "@types/bootstrap": "^5.2.10", "@xexiu/astro-accordion": "^0.0.10", "astro": "^4.6.2", "astro-bootstrap": "^0.6.2", "auth-astro": "^4.1.1", - "better-sqlite3": "^9.2.2", + "better-sqlite3": "^11.0.0", "bootstrap": "^5.3.3", "lucia": "^3.2.0", - "oslo": "^1.0.3", + "oslo": "^1.2.0", "tailwindcss": "^3.4.3", "typescript": "^5.4.5" }, diff --git a/src/components/Dummy.astro b/src/components/Dummy.astro index 4c9e4ad..99ab06f 100644 --- a/src/components/Dummy.astro +++ b/src/components/Dummy.astro @@ -15,7 +15,7 @@ var test = await Astro.glob("../content/accordion/*.md"); </svg> </div> <div class="answer"> - <Content /> + <Content/> </div> </div> )}) @@ -92,7 +92,7 @@ var test = await Astro.glob("../content/accordion/*.md"); } svg{ - display: none; /* If you want the icon besides the accordion, remove this line */ + display: none; /* If you want an icon besides the accordion, remove this line */ transition: transform 0.5s ease-in; } @@ -123,8 +123,25 @@ var test = await Astro.glob("../content/accordion/*.md"); <script> - const faqs = document.querySelectorAll('.faq'); + var answers = Array.from(document.getElementsByClassName("answer")); + // console.log(answers); + answers.forEach(answer => { + const anchors = answer.querySelectorAll("ul li a"); + const lists = answer.querySelectorAll("ul li"); + // console.log(anchors); + // console.log(lists); + anchors.forEach(anchor => { + anchor.classList.add("text-[#17a2b8]", "no-underline", "hover:underline", "hover:text-[#0e8fa3]"); + }); + + lists.forEach((list)=>{ + list.classList.add("py-1", "px-3", "text-base", "m-0", "leading-7", "text-black", "font-medium", "bg-white"); + }) +}); + + + const faqs = document.querySelectorAll('.faq'); faqs.forEach(faq => { faq.addEventListener('click', () => { faq.classList.toggle('active'); diff --git a/src/components/IconBar.astro b/src/components/IconBar.astro index 0f96fb0..f1919c3 100644 --- a/src/components/IconBar.astro +++ b/src/components/IconBar.astro @@ -35,7 +35,7 @@ const props = Astro.props </svg> </a> - </div> +</div> <style> diff --git a/src/components/Login.astro b/src/components/Login.astro deleted file mode 100644 index 42468cc..0000000 --- a/src/components/Login.astro +++ /dev/null @@ -1,15 +0,0 @@ ---- ---- - -<div class="bg-white p-10 rounded-lg shadow-lg w-full max-w-sm"> - <h1 class="text-2xl font-bold text-gray-800 mb-6 text-center">Admin Login</h1> - <form> - <div class="mb-4"> - <input type="text" placeholder="Username" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-600"> - </div> - <div class="mb-6"> - <input type="password" placeholder="Password" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-600"> - </div> - <button type="submit" class="w-full bg-purple-600 text-white py-2 rounded-lg hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-600">Login</button> - </form> -</div>
\ No newline at end of file diff --git a/src/components/LoginBody.astro b/src/components/LoginBody.astro new file mode 100644 index 0000000..188cfd6 --- /dev/null +++ b/src/components/LoginBody.astro @@ -0,0 +1,51 @@ +--- +const user = Astro.locals.user; // Fetch the user from session or locals + +if (user) { + console.log(user); + return Astro.redirect('/admin'); +} + +--- +<div class="flex items-center justify-center h-screen"> + <div class="bg-white p-10 rounded-lg shadow-lg w-full max-w-sm"> + <h1 class="text-2xl font-bold text-gray-800 mb-6 text-center">Admin Login</h1> + <form method="post" action="/api/login"> + <div class="mb-4"> + <input type="text" id="username" placeholder="Username" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-600"> + </div> + <div class="mb-6"> + <input type="password" id="password" placeholder="Password" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-600"> + </div> + <button type="submit" class="w-full bg-blue-500 hover:bg-blue-700 text-white py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-600"> + Login + </button> + <p id="form-error"></p> + </form> + <a class="no-underline text-white" href="/signup"> + <button class="w-full bg-blue-500 hover:bg-blue-700 text-white py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-600"> + Signup + </button> + </a> + </div> +</div> + +<script> + const errorMessageElement = document.getElementById("form-error")!; + + document.forms[0].addEventListener("submit", async (e) => { + e.preventDefault(); + errorMessageElement.innerText = ""; + const formElement = e.target as HTMLFormElement; + const response = await fetch(formElement.action, { + method: formElement.method, + body: new FormData(formElement), + credentials: 'include' + }); + if (response.ok) { + window.location.href = "/admin"; + } else { + errorMessageElement.innerText = (await response.json()).error; + } + }); +</script>
\ No newline at end of file diff --git a/src/components/ResultsBody.astro b/src/components/ResultsBody.astro index 445caa4..8dbc21c 100644 --- a/src/components/ResultsBody.astro +++ b/src/components/ResultsBody.astro @@ -1,21 +1,38 @@ --- -import Login from "./Login.astro"; - +// import Login from "./Login.astro"; +const user = Astro.locals.user; +if(!user) { + Astro.redirect('/login'); +} +else{ + Astro.redirect('/admin'); +} --- <div class="resultsContainer"> <div class="pre min-h-screen flex flex-col items-center justify-center"> <h1 class="mb-10">Results will be announced soon</h1> - <Login /> + <!-- <Login /> --> + <a href = "/login" type="submit" class="w-40 bg-purple-600 no-underline text-center text-white py-2 rounded-lg hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-600" id="Adminbtn">Admin Login<a/> </div> <div class="post min-h-screen hidden"> <h1>You got 100%</h1> + <a href="/admin" class="w-40 bg-purple-600 no-underline text-center text-white py-2 rounded-lg hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-600">Check your Results</a> </div> </div> <style> </style> <script> + +// Redirect not working + +// document.getElementById("Adminbtn")?.addEventListener("click", function() { +// console.log("Admin Login"); +// Astro.redirect('/login'); +// }); + + var resultDate = { year: 2024, month: 6, @@ -70,6 +87,7 @@ function checkTime() { document.querySelector(".pre")?.classList.add("hidden"); document.querySelector(".post")?.classList.remove("hidden"); document.querySelector(".post")?.classList.add("flex"); + document.querySelector(".post")?.classList.add("flex-col"); document.querySelector(".post")?.classList.add("items-center"); document.querySelector(".post")?.classList.add("justify-center"); } else { diff --git a/src/components/SignupBody.astro b/src/components/SignupBody.astro new file mode 100644 index 0000000..0601c3d --- /dev/null +++ b/src/components/SignupBody.astro @@ -0,0 +1,44 @@ +--- + +--- + +<div class="flex flex-col items-center justify-center min-h-screen bg-gray-100"> + <h1 class="text-2xl font-bold mb-6">Create an account</h1> + <form method="post" action="/api/signup" class="w-full max-w-sm bg-white p-8 rounded shadow-md"> + <div class="mb-4"> + <label for="username" class="block text-gray-700 text-sm font-bold mb-2">Username</label> + <input name="username" id="username" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" /> + </div> + <div class="mb-4"> + <label for="password" class="block text-gray-700 text-sm font-bold mb-2">Password</label> + <input type="password" name="password" id="password" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" /> + </div> + <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Continue</button> + <p id="form-error" class="mt-4 text-red-500 text-sm"></p> + </form> + <a href="/login" class="mt-4 text-blue-500 hover:underline"> + <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"> + Sign in + </button> +</a> +</div> + + +<script> + const errorMessageElement = document.getElementById("form-error")!; + + document.forms[0].addEventListener("submit", async (e) => { + e.preventDefault(); + errorMessageElement.innerText = ""; + const formElement = e.target as HTMLFormElement; + const response = await fetch(formElement.action, { + method: formElement.method, + body: new FormData(formElement) + }); + if (response.ok) { + window.location.href = "/admin"; + } else { + errorMessageElement.innerText = (await response.json()).error; + } + }); +</script>
\ No newline at end of file diff --git a/src/content/accordion/a1.md b/src/content/accordion/a1.md index 7cd910d..b8d9a1d 100644 --- a/src/content/accordion/a1.md +++ b/src/content/accordion/a1.md @@ -3,5 +3,5 @@ Number: 1 heading: "FOSSEE and Spoken Tutorial" --- -- [1. FOSSEE and Spoken Tutorial](https://spoken-tutorial.org/tutorial-search/?search_foss=Arduino&search_language=English) +- [1. Arduino Tutorial Series by Spoken Tutorial](https://spoken-tutorial.org/tutorial-search/?search_foss=Arduino&search_language=English) - [2. FLOSS-Arduino books teach Arduino control via open-source software](https://floss-arduino.fossee.in/) diff --git a/src/env.d.ts b/src/env.d.ts index acef35f..9215d58 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,2 +1,8 @@ /// <reference path="../.astro/types.d.ts" /> /// <reference types="astro/client" /> +declare namespace App { + interface Locals { + session: import("lucia").Session | null; + user: import("lucia").User | null; + } +} diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..77924ae --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,30 @@ +import { Lucia } from "lucia"; +import { BetterSqlite3Adapter } from "@lucia-auth/adapter-sqlite"; +import { db } from "./db"; + +import type { DatabaseUser } from "./db"; + +const adapter = new BetterSqlite3Adapter(db, { + user: "user", + session: "session", +}); + +export const lucia = new Lucia(adapter, { + sessionCookie: { + attributes: { + secure: import.meta.env.PROD, + }, + }, + getUserAttributes: (attributes) => { + return { + username: attributes.username, + }; + }, +}); + +declare module "lucia" { + interface Register { + Lucia: typeof lucia; + DatabaseUserAttributes: Omit<DatabaseUser, "id">; + } +} diff --git a/src/lib/databaseUpdates.js b/src/lib/databaseUpdates.js new file mode 100644 index 0000000..9eb6055 --- /dev/null +++ b/src/lib/databaseUpdates.js @@ -0,0 +1,39 @@ +import sqlite from "better-sqlite3"; + +// Path to your SQLite database file +const dbPath = "./mydatabase.db"; + +// Open the SQLite database connection +const db = sqlite(dbPath); + +const userId = "p1pczcq94xsbdcj"; + +// Function to print all entries from the user table +function printAllUsers() { + const users = db.prepare("SELECT * FROM user").all(); + console.log("All Users:"); + console.table(users); +} + +// Disable foreign key constraints +db.pragma("foreign_keys = OFF"); + +// Perform the delete operation +const result = db.prepare("DELETE FROM user WHERE id = ?").run(userId); + +// Re-enable foreign key constraints +db.pragma("foreign_keys = ON"); + +// Function to delete a user by ID +function deleteUserById(userId) { + const result = db.prepare("DELETE FROM user WHERE id = ?").run(userId); + if (result.changes > 0) { + console.log(`User with ID ${userId} deleted successfully.`); + } else { + console.log(`No user found with ID ${userId}.`); + } +} + +// Example usage: +printAllUsers(); +deleteUserById(userId); diff --git a/src/lib/db.ts b/src/lib/db.ts new file mode 100644 index 0000000..002c215 --- /dev/null +++ b/src/lib/db.ts @@ -0,0 +1,27 @@ +import sqlite from "better-sqlite3"; +// import path from "path"; + +const dbPath = "./mydatabase.db"; + +export const db = sqlite(dbPath); + +// Create the tables if they do not exist +db.exec(`CREATE TABLE IF NOT EXISTS user ( + id TEXT NOT NULL PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL +)`); + +db.exec(`CREATE TABLE IF NOT EXISTS session ( + id TEXT NOT NULL PRIMARY KEY, + expires_at INTEGER NOT NULL, + user_id TEXT NOT NULL, + FOREIGN KEY (user_id) REFERENCES user(id) +)`); + +// Export the DatabaseUser interface +export interface DatabaseUser { + id: string; + username: string; + password: string; +} diff --git a/src/middleware.ts b/src/middleware.ts index 03d5b85..3e8bd01 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,47 +1,47 @@ -// import { lucia } from "./lib/auth"; -// import { verifyRequestOrigin } from "lucia"; -// import { defineMiddleware } from "astro:middleware"; +import { lucia } from "./lib/auth"; +import { verifyRequestOrigin } from "lucia"; +import { defineMiddleware } from "astro:middleware"; -// export const onRequest = defineMiddleware(async (context, next) => { -// if (context.request.method !== "GET") { -// const originHeader = context.request.headers.get("Origin"); -// const hostHeader = context.request.headers.get("Host"); -// if ( -// !originHeader || -// !hostHeader || -// !verifyRequestOrigin(originHeader, [hostHeader]) -// ) { -// return new Response(null, { -// status: 403, -// }); -// } -// } +export const onRequest = defineMiddleware(async (context, next) => { + if (context.request.method !== "GET") { + const originHeader = context.request.headers.get("Origin"); + const hostHeader = context.request.headers.get("Host"); + if ( + !originHeader || + !hostHeader || + !verifyRequestOrigin(originHeader, [hostHeader]) + ) { + return new Response(null, { + status: 403, + }); + } + } -// const sessionId = context.cookies.get(lucia.sessionCookieName)?.value ?? null; -// if (!sessionId) { -// context.locals.user = null; -// context.locals.session = null; -// return next(); -// } + const sessionId = context.cookies.get(lucia.sessionCookieName)?.value ?? null; + if (!sessionId) { + context.locals.user = null; + context.locals.session = null; + return next(); + } -// const { session, user } = await lucia.validateSession(sessionId); -// if (session && session.fresh) { -// const sessionCookie = lucia.createSessionCookie(session.id); -// context.cookies.set( -// sessionCookie.name, -// sessionCookie.value, -// sessionCookie.attributes -// ); -// } -// if (!session) { -// const sessionCookie = lucia.createBlankSessionCookie(); -// context.cookies.set( -// sessionCookie.name, -// sessionCookie.value, -// sessionCookie.attributes -// ); -// } -// context.locals.session = session; -// context.locals.user = user; -// return next(); -// }); + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + const sessionCookie = lucia.createSessionCookie(session.id); + context.cookies.set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + } + if (!session) { + const sessionCookie = lucia.createBlankSessionCookie(); + context.cookies.set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + } + context.locals.session = session; + context.locals.user = user; + return next(); +}); diff --git a/src/pages/admin.astro b/src/pages/admin.astro new file mode 100644 index 0000000..32aad9b --- /dev/null +++ b/src/pages/admin.astro @@ -0,0 +1,46 @@ +--- +import Layout from '../layouts/Layout.astro'; +import TopBar from '../components/TopBar.astro'; +import Navbar from '../components/Navbar.astro'; +import Footer from '../components/Footer.astro'; +import IconBar from '../components/IconBar.astro'; +const user = Astro.locals.user; +if (!user) { + return Astro.redirect("/login"); +} + +const username = user.username; +--- + +<Layout title="Welcome to FOSSEE."> + <main> + <TopBar /> + <Navbar /> + <IconBar/> + <div> + <h1>Hi, {user.username}!</h1> + <p>Your user ID is {user.id}.</p> + <form method="post" action="/api/logout"> + <button>Sign out</button> + </form> + </div> + <Footer/> + + </main> +</Layout> + +<style> + +</style> + +<script> + document.forms[0].addEventListener("submit", async (e) => { + e.preventDefault(); + const formElement = e.target as HTMLFormElement; + await fetch(formElement.action, { + method: formElement.method, + body: new FormData(formElement) + }); + window.location.href = "/login"; + }); +</script> diff --git a/src/pages/api/login.ts b/src/pages/api/login.ts new file mode 100644 index 0000000..701c356 --- /dev/null +++ b/src/pages/api/login.ts @@ -0,0 +1,71 @@ +import { lucia } from "../../lib/auth"; +import { Argon2id } from "oslo/password"; +import { db } from "../../lib/db"; + +import type { DatabaseUser } from "../../lib/db"; +import type { APIContext } from "astro"; + +export async function POST(context: APIContext): Promise<Response> { + const formData = await context.request.formData(); + const username = formData.get("username"); + if ( + typeof username !== "string" || + username.length < 3 || + username.length > 31 || + !/^[a-z0-9_-]+$/.test(username) + ) { + return new Response(JSON.stringify({ error: "Invalid username" }), { + status: 400, + }); + } + const password = formData.get("password"); + if ( + typeof password !== "string" || + password.length < 6 || + password.length > 255 + ) { + return new Response(JSON.stringify({ error: "Invalid password" }), { + status: 400, + }); + } + + const existingUser = db + .prepare("SELECT * FROM user WHERE username = ?") + .get(username) as DatabaseUser | undefined; + + if (!existingUser) { + return new Response( + JSON.stringify({ + error: "Incorrect username or password", + }), + { + status: 400, + } + ); + } + + const validPassword = await new Argon2id().verify( + existingUser.password, + password + ); + if (!validPassword) { + return new Response( + JSON.stringify({ + error: "Incorrect username or password", + }), + { + status: 400, + } + ); + } + + const session = await lucia.createSession(existingUser.id, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + context.cookies.set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + + return context.redirect("/admin"); +} diff --git a/src/pages/api/logout.ts b/src/pages/api/logout.ts new file mode 100644 index 0000000..d99db42 --- /dev/null +++ b/src/pages/api/logout.ts @@ -0,0 +1,22 @@ +import { lucia } from "../../lib/auth"; + +import type { APIContext } from "astro"; + +export async function POST(context: APIContext): Promise<Response> { + if (!context.locals.session) { + return new Response(null, { + status: 401, + }); + } + + await lucia.invalidateSession(context.locals.session.id); + + const sessionCookie = lucia.createBlankSessionCookie(); + context.cookies.set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + + return new Response(); +} diff --git a/src/pages/api/signup.ts b/src/pages/api/signup.ts new file mode 100644 index 0000000..8646a1e --- /dev/null +++ b/src/pages/api/signup.ts @@ -0,0 +1,85 @@ +import { lucia } from "../../lib/auth"; +import { generateId } from "lucia"; +import { Argon2id } from "oslo/password"; +import { db } from "../../lib/db"; +import { SqliteError } from "better-sqlite3"; + +import type { APIContext } from "astro"; + +export async function POST(context: APIContext): Promise<Response> { + const formData = await context.request.formData(); + const username = formData.get("username"); + // username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, -, and _ + // keep in mind some database (e.g. mysql) are case insensitive + if ( + typeof username !== "string" || + username.length < 3 || + username.length > 31 || + !/^[a-z0-9_-]+$/.test(username) + ) { + return new Response( + JSON.stringify({ + error: "Invalid username", + }), + { + status: 400, + } + ); + } + const password = formData.get("password"); + if ( + typeof password !== "string" || + password.length < 6 || + password.length > 255 + ) { + return new Response( + JSON.stringify({ + error: "Invalid password", + }), + { + status: 400, + } + ); + } + + const hashedPassword = await new Argon2id().hash(password); + const userId = generateId(15); + + try { + db.prepare("INSERT INTO user (id, username, password) VALUES(?, ?, ?)").run( + userId, + username, + hashedPassword + ); + + const session = await lucia.createSession(userId, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + context.cookies.set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + + return new Response(); + } catch (e) { + if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + return new Response( + JSON.stringify({ + error: "Username already used", + }), + { + status: 400, + } + ); + } + return new Response( + JSON.stringify({ + error: "An unknown error occurred", + }), + { + status: 500, + } + ); + } + return context.redirect("/admin"); +} diff --git a/src/pages/login.astro b/src/pages/login.astro new file mode 100644 index 0000000..30ccfce --- /dev/null +++ b/src/pages/login.astro @@ -0,0 +1,22 @@ +--- +import Layout from '../layouts/Layout.astro'; +import TopBar from '../components/TopBar.astro'; +import Navbar from '../components/Navbar.astro'; +import Footer from '../components/Footer.astro'; +import IconBar from '../components/IconBar.astro'; +import LoginBody from '../components/LoginBody.astro'; +--- + +<Layout title="Welcome to FOSSEE."> + <main> + <TopBar /> + <Navbar /> + <IconBar /> + <LoginBody/> + <Footer/> + </main> +</Layout> + +<style> + +</style> diff --git a/src/pages/signup.astro b/src/pages/signup.astro new file mode 100644 index 0000000..5b19cfe --- /dev/null +++ b/src/pages/signup.astro @@ -0,0 +1,23 @@ +--- +import Layout from '../layouts/Layout.astro'; +import TopBar from '../components/TopBar.astro'; +import Navbar from '../components/Navbar.astro'; +import Footer from '../components/Footer.astro'; + +import IconBar from '../components/IconBar.astro'; +import SignupBody from '../components/SignupBody.astro'; +--- + +<Layout title="Welcome to FOSSEE."> + <main> + <TopBar /> + <Navbar /> + <IconBar /> + <SignupBody/> + <Footer/> + </main> +</Layout> + +<style> + +</style> |