summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManurbhav2024-06-10 18:33:48 +0530
committerManurbhav2024-06-10 18:33:48 +0530
commit0cbdfccf08f51ecc7ec4b0cc985e84df7a179fd1 (patch)
tree9f3d1c3b63589166039dec7a1c65a279322a276b
parentdf08439fe9a02def62de129b67782f2014418f0a (diff)
downloadIotJS-Astro-0cbdfccf08f51ecc7ec4b0cc985e84df7a179fd1.tar.gz
IotJS-Astro-0cbdfccf08f51ecc7ec4b0cc985e84df7a179fd1.tar.bz2
IotJS-Astro-0cbdfccf08f51ecc7ec4b0cc985e84df7a179fd1.zip
Auth setup (partial)
Incomplete right now
-rw-r--r--.gitignore3
-rw-r--r--package-lock.json12
-rw-r--r--package.json6
-rw-r--r--src/components/Dummy.astro23
-rw-r--r--src/components/IconBar.astro2
-rw-r--r--src/components/Login.astro15
-rw-r--r--src/components/LoginBody.astro51
-rw-r--r--src/components/ResultsBody.astro24
-rw-r--r--src/components/SignupBody.astro44
-rw-r--r--src/content/accordion/a1.md2
-rw-r--r--src/env.d.ts6
-rw-r--r--src/lib/auth.ts30
-rw-r--r--src/lib/databaseUpdates.js39
-rw-r--r--src/lib/db.ts27
-rw-r--r--src/middleware.ts88
-rw-r--r--src/pages/admin.astro46
-rw-r--r--src/pages/api/login.ts71
-rw-r--r--src/pages/api/logout.ts22
-rw-r--r--src/pages/api/signup.ts85
-rw-r--r--src/pages/login.astro22
-rw-r--r--src/pages/signup.astro23
21 files changed, 565 insertions, 76 deletions
diff --git a/.gitignore b/.gitignore
index 016b59e..bf35e9d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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>