浏览代码

login working

C4illin 1 年之前
父节点
当前提交
c2f36e9723
共有 8 个文件被更改,包括 279 次插入52 次删除
  1. 0 15
      README.md
  2. 二进制
      bun.lockb
  3. 8 5
      package.json
  4. 151 4
      src/index.ts
  5. 25 26
      src/pages/index.html
  6. 40 0
      src/pages/login.html
  7. 36 0
      src/pages/register.html
  8. 19 2
      src/public/script.js

+ 0 - 15
README.md

@@ -1,15 +0,0 @@
-# Elysia with Bun runtime
-
-## Getting Started
-To get started with this template, simply paste this command into your terminal:
-```bash
-bun create elysia ./elysia-example
-```
-
-## Development
-To start the development server run:
-```bash
-bun run dev
-```
-
-Open http://localhost:3000/ with your browser to see the result.

二进制
bun.lockb


+ 8 - 5
package.json

@@ -6,16 +6,19 @@
     "dev": "bun run --hot src/index.ts"
   },
   "dependencies": {
+    "@elysiajs/cookie": "^0.8.0",
     "@elysiajs/html": "^1.0.2",
+    "@elysiajs/jwt": "^1.0.2",
     "@elysiajs/static": "^1.0.2",
-    "elysia": "latest",
-    "nanoid": "^5.0.7"
-  },
-  "devDependencies": {
-    "bun-types": "latest"
+    "elysia": "latest"
   },
   "module": "src/index.js",
   "bun-create": {
     "start": "bun run src/index.ts"
+  },
+  "devDependencies": {
+    "@types/bun": "^1.1.2",
+    "@types/node": "^20.12.12",
+    "bun-types": "latest"
   }
 }

+ 151 - 4
src/index.ts

@@ -1,13 +1,45 @@
-import { Elysia } from "elysia";
+import { Elysia, t } from "elysia";
 import { staticPlugin } from "@elysiajs/static";
 import { html } from "@elysiajs/html";
 import { Database } from "bun:sqlite";
+import cookie from "@elysiajs/cookie";
+import { unlink } from "node:fs/promises";
+import { randomUUID } from "node:crypto";
+import { jwt } from "@elysiajs/jwt";
+// import { Lucia } from "lucia";
+// import { BunSQLiteAdapter } from "@lucia-auth/adapter-sqlite";
 
 const db = new Database("./mydb.sqlite");
-const baseDir = import.meta.dir;
 const uploadsDir = "./uploads/";
 
+// init db
+db.exec(`
+CREATE TABLE IF NOT EXISTS users (
+	id INTEGER PRIMARY KEY AUTOINCREMENT,
+	email TEXT NOT NULL,
+	password TEXT NOT NULL
+);
+`);
+
+const basicAuthModel = new Elysia().model({
+	basicAuthModel: t.Object({
+		email: t.String(),
+		password: t.String(),
+	}),
+});
+
 const app = new Elysia()
+	.use(cookie())
+	.use(
+		jwt({
+			name: "jwt",
+			schema: t.Object({
+				id: t.String(),
+			}),
+			secret: "secret",
+			exp: "7d",
+		}),
+	)
 	.use(html())
 	.use(
 		staticPlugin({
@@ -15,14 +47,126 @@ const app = new Elysia()
 			prefix: "/",
 		}),
 	)
-	.get("/", () => Bun.file("src/pages/index.html"))
+	.get("/register", async () => {
+		return Bun.file("src/pages/register.html");
+	})
+	.post(
+		"/register",
+		async function handler({ body, set, jwt, cookie: { auth } }) {
+			const existingUser = await db
+				.query("SELECT * FROM users WHERE email = ?")
+				.get(body.email);
+			if (existingUser) {
+				set.status = 400;
+				return {
+					message: "Email already in use.",
+				};
+			}
+			const savedPassword = await Bun.password.hash(body.password);
+
+			db.run(
+				"INSERT INTO users (email, password) VALUES (?, ?)",
+				body.email,
+				savedPassword,
+			);
+
+			const user = await db
+				.query("SELECT * FROM users WHERE email = ?")
+				.get(body.email);
+
+			const accessToken = await jwt.sign({
+				id: String(user.id),
+			});
+
+			// set cookie
+			auth.set({
+				value: accessToken,
+				httpOnly: true,
+				secure: true,
+				maxAge: 60 * 60 * 24 * 7,
+				sameSite: "strict",
+			});
+
+			// redirect to home
+			set.status = 302;
+			set.headers = {
+				Location: "/",
+			};
+		},
+	)
+	.get("/login", async () => {
+		return Bun.file("src/pages/login.html");
+	})
+	.post("/login", async function handler({ body, set, jwt, cookie: { auth } }) {
+		const existingUser = await db
+			.query("SELECT * FROM users WHERE email = ?")
+			.get(body.email);
+
+		if (!existingUser) {
+			set.status = 403;
+			return {
+				message: "Invalid credentials.",
+			};
+		}
+
+		const validPassword = await Bun.password.verify(
+			body.password,
+			existingUser.password,
+		);
+
+		if (!validPassword) {
+			set.status = 403;
+			return {
+				message: "Invalid credentials.",
+			};
+		}
+
+		const accessToken = await jwt.sign({
+			id: String(existingUser.id),
+		});
+
+		// set cookie
+		// set cookie
+		auth.set({
+			value: accessToken,
+			httpOnly: true,
+			secure: true,
+			maxAge: 60 * 60 * 24 * 7,
+			sameSite: "strict",
+		});
+
+		// redirect to home
+		set.status = 302;
+		set.headers = {
+			Location: "/",
+		};
+	})
+	.post("/logout", async ({ set, cookie: { auth } }) => {
+		auth.remove();
+		set.status = 302;
+		set.headers = {
+			Location: "/login",
+		};
+	})
+	.get("/", async ({ jwt, set, cookie: { auth } }) => {
+		// validate jwt
+		const user = await jwt.verify(auth.value);
+		if (!user) {
+			// redirect to login
+			set.status = 302;
+			set.headers = {
+				Location: "/login",
+			};
+			return;
+		}
+		return Bun.file("src/pages/index.html");
+	})
 	.post("/upload", async (ctx) => {
 		console.log(ctx.body);
 		if (ctx.body?.file) {
 			await Bun.write(`${uploadsDir}${ctx.body.file.name}`, ctx.body.file);
 		} else if (ctx.body?.files) {
 			if (Array.isArray(ctx.body.files)) {
-				console.log("Found array of files");
 				for (const file of ctx.body.files) {
 					console.log(file);
 					await Bun.write(`${uploadsDir}${file.name}`, file);
@@ -32,6 +176,9 @@ const app = new Elysia()
 			}
 		}
 	})
+	.post("/delete/:file", async (ctx) => {
+		await unlink(`${uploadsDir}${ctx.params.file}`);
+	})
 	.listen(3000);
 
 console.log(

+ 25 - 26
src/pages/index.html

@@ -7,14 +7,14 @@
   <title>ConvertX</title>
   <link rel="stylesheet" href="pico.lime.min.css">
   <link rel="stylesheet" href="style.css">
-  <script src="index.js" defer></script>
+  <script src="script.js" defer></script>
 </head>
 
 <body>
   <header class="container-fluid">
     <nav>
       <ul>
-        <li><strong>ConvertX</strong></li>
+        <li><a href="/">ConvertX</a></strong></li>
       </ul>
       <ul>
         <li><a href="#">About</a></li>
@@ -27,30 +27,29 @@
   <main class="container-fluid">
 
     <!-- File upload -->
-    <form method="post" action="upload" enctype="multipart/form-data">
-      <div>
-        <article>
-          <table id="file-list">
-          </table>
-          <input type="file" name="file" multiple />
-
-        </article>
-        <!-- <div class="icon">></div> -->
-        <article>
-          <select name="to" aria-label="Convert to" required>
-            <option selected disabled value="">Convert to</option>
-            <option>JPG</option>
-            <option>PNG</option>
-            <option>SVG</option>
-            <option>PDF</option>
-            <option>DOCX</option>
-          </select>
-        </article>
-      </div>
-      <div class="center">
-        <input type="submit" value="Convert">
-        <!-- <button type="submit">Convert</button> -->
-      </div>
+
+
+    <article>
+      <table id="file-list">
+      </table>
+      <input type="file" name="file" multiple />
+
+    </article>
+    <!-- <div class="icon">></div> -->
+    <form method="post"></form>
+    <article>
+      <select name="to" aria-label="Convert to" required>
+        <option selected disabled value="">Convert to</option>
+        <option>JPG</option>
+        <option>PNG</option>
+        <option>SVG</option>
+        <option>PDF</option>
+        <option>DOCX</option>
+      </select>
+    </article>
+      <input type="submit" value="Convert">
+      <!-- <button type="submit">Convert</button> -->
+    <!-- </div> -->
     </form>
   </main>
   <footer></footer>

+ 40 - 0
src/pages/login.html

@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>ConvertX | Login</title>
+  <link rel="stylesheet" href="pico.lime.min.css">
+  <link rel="stylesheet" href="style.css">
+</head>
+
+<body>
+  <header class="container-fluid">
+    <nav>
+      <ul>
+        <li><a href="/">ConvertX</a></strong></li>
+      </ul>
+      <ul>
+        <li><a href="#">About</a></li>
+        <li><a href="#">Services</a></li>
+        <li><button class="secondary">Products</button></li>
+      </ul>
+    </nav>
+  </header>
+
+  <main class="container-fluid">
+    <form method="post">
+      <input type="email" name="email" placeholder="Email" required>
+      <input type="password" name="password" placeholder="Password" required>
+      <div role="group">
+        <a href="/register" role="button" class="secondary">Register an account</a>
+        <input type="submit" value="Login">
+      </div>
+    </form>
+
+  </main>
+
+</body>
+
+</html>

+ 36 - 0
src/pages/register.html

@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>ConvertX | Register</title>
+  <link rel="stylesheet" href="pico.lime.min.css">
+  <link rel="stylesheet" href="style.css">
+</head>
+
+<body>
+  <header class="container-fluid">
+    <nav>
+      <ul>
+        <li><a href="/">ConvertX</a></strong></li>
+      </ul>
+      <ul>
+        <li><a href="#">About</a></li>
+        <li><a href="#">Services</a></li>
+        <li><button class="secondary">Products</button></li>
+      </ul>
+    </nav>
+  </header>
+
+  <main class="container-fluid">
+    <form method="post">
+      <input type="email" name="email" placeholder="Email" required>
+      <input type="password" name="password" placeholder="Password" required>
+      <input type="submit" value="Register">
+    </form>
+  </main>
+
+</body>
+
+</html>

+ 19 - 2
src/public/index.js → src/public/script.js

@@ -1,7 +1,7 @@
 // Select the file input element
 const fileInput = document.querySelector('input[type="file"]');
 
-let filesToUpload = [];
+const filesToUpload = [];
 
 // Add a 'change' event listener to the file input element
 fileInput.addEventListener("change", (e) => {
@@ -19,7 +19,7 @@ fileInput.addEventListener("change", (e) => {
 		row.innerHTML = `
       <td>${files[i].name}</td>
       <td>${(files[i].size / 1024 / 1024).toFixed(2)} MB</td>
-      <td><button class="secondary">x</button></td>
+      <td><button class="secondary" onclick="deleteRow(this)">x</button></td>
     `;
 
 		// Append the row to the file-list table
@@ -29,6 +29,23 @@ fileInput.addEventListener("change", (e) => {
 	uploadFiles(files);
 });
 
+// Add a onclick for the delete button
+const deleteRow = (target) => {
+	const fileName = target.parentElement.parentElement.children[0].textContent;
+	const row = target.parentElement.parentElement;
+	row.remove();
+	
+	fetch("/delete", {
+		method: "POST",
+		body: JSON.stringify({ fileName }),
+	})
+		.then((res) => res.json())
+		.then((data) => {
+			console.log(data);
+		})
+		.catch((err) => console.log(err));
+};
+
 const uploadFiles = (files) => {
 	const formData = new FormData();