Explorar el Código

Create login, create 404 page

Lee Morgan hace 2 semanas
padre
commit
b39023d051

+ 1 - 0
example.env

@@ -1 +1,2 @@
 MONGODB_URI
+JWT_SECRET

+ 133 - 0
package-lock.json

@@ -9,6 +9,7 @@
 			"version": "0.0.1",
 			"dependencies": {
 				"bcrypt": "^6.0.0",
+				"jsonwebtoken": "^9.0.3",
 				"mongoose": "^9.9.4",
 				"validator": "^13.15.35"
 			},
@@ -566,6 +567,12 @@
 				"node": ">=20.19.0"
 			}
 		},
+		"node_modules/buffer-equal-constant-time": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+			"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+			"license": "BSD-3-Clause"
+		},
 		"node_modules/clsx": {
 			"version": "2.1.1",
 			"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -613,6 +620,15 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/ecdsa-sig-formatter": {
+			"version": "1.0.11",
+			"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+			"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+			"license": "Apache-2.0",
+			"dependencies": {
+				"safe-buffer": "^5.0.1"
+			}
+		},
 		"node_modules/esm-env": {
 			"version": "1.2.2",
 			"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
@@ -681,6 +697,49 @@
 				"@types/estree": "^1.0.6"
 			}
 		},
+		"node_modules/jsonwebtoken": {
+			"version": "9.0.3",
+			"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+			"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+			"license": "MIT",
+			"dependencies": {
+				"jws": "^4.0.1",
+				"lodash.includes": "^4.3.0",
+				"lodash.isboolean": "^3.0.3",
+				"lodash.isinteger": "^4.0.4",
+				"lodash.isnumber": "^3.0.3",
+				"lodash.isplainobject": "^4.0.6",
+				"lodash.isstring": "^4.0.1",
+				"lodash.once": "^4.0.0",
+				"ms": "^2.1.1",
+				"semver": "^7.5.4"
+			},
+			"engines": {
+				"node": ">=12",
+				"npm": ">=6"
+			}
+		},
+		"node_modules/jwa": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+			"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+			"license": "MIT",
+			"dependencies": {
+				"buffer-equal-constant-time": "^1.0.1",
+				"ecdsa-sig-formatter": "1.0.11",
+				"safe-buffer": "^5.0.1"
+			}
+		},
+		"node_modules/jws": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+			"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+			"license": "MIT",
+			"dependencies": {
+				"jwa": "^2.0.1",
+				"safe-buffer": "^5.0.1"
+			}
+		},
 		"node_modules/kareem": {
 			"version": "3.3.0",
 			"resolved": "https://registry.npmjs.org/kareem/-/kareem-3.3.0.tgz",
@@ -980,6 +1039,48 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/lodash.includes": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+			"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+			"license": "MIT"
+		},
+		"node_modules/lodash.isboolean": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+			"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+			"license": "MIT"
+		},
+		"node_modules/lodash.isinteger": {
+			"version": "4.0.4",
+			"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+			"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+			"license": "MIT"
+		},
+		"node_modules/lodash.isnumber": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+			"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+			"license": "MIT"
+		},
+		"node_modules/lodash.isplainobject": {
+			"version": "4.0.6",
+			"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+			"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+			"license": "MIT"
+		},
+		"node_modules/lodash.isstring": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+			"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+			"license": "MIT"
+		},
+		"node_modules/lodash.once": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+			"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+			"license": "MIT"
+		},
 		"node_modules/magic-string": {
 			"version": "0.30.21",
 			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -1256,6 +1357,38 @@
 				"@rolldown/binding-win32-x64-msvc": "1.2.6"
 			}
 		},
+		"node_modules/safe-buffer": {
+			"version": "5.2.1",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+			"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			],
+			"license": "MIT"
+		},
+		"node_modules/semver": {
+			"version": "7.8.5",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+			"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+			"license": "ISC",
+			"bin": {
+				"semver": "bin/semver.js"
+			},
+			"engines": {
+				"node": ">=10"
+			}
+		},
 		"node_modules/set-cookie-parser": {
 			"version": "3.1.2",
 			"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz",

+ 1 - 0
package.json

@@ -18,6 +18,7 @@
 	},
 	"dependencies": {
 		"bcrypt": "^6.0.0",
+		"jsonwebtoken": "^9.0.3",
 		"mongoose": "^9.9.4",
 		"validator": "^13.15.35"
 	}

+ 4 - 0
src/lib/server/models/User.js

@@ -12,6 +12,10 @@ const userSchema = new mongoose.Schema({
     password: {
         type: String,
         required: true
+    },
+    token: {
+        type: String,
+        required: true
     }
 });
 

+ 57 - 0
src/routes/+error.svelte

@@ -0,0 +1,57 @@
+<script>
+    import {page} from "$app/state";
+</script>
+
+<div class="container">
+    <div class="content">
+        <p class="code">{page.status}</p>
+        <h1>{page.status === 404 ? "Page not found" : "Something went wrong"}</h1>
+        <p class="message">
+            {page.status === 404
+                ? "We couldn't find the recipe or page you were looking for."
+                : (page.error?.message ?? "An unexpected error occurred.")}
+        </p>
+        <a href="/" class="button">Back to Home</a>
+    </div>
+</div>
+
+<style>
+    .container{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 100%;
+        height: calc(100% - 75px);
+        text-align: center;
+    }
+
+    .content{
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 0.75rem;
+        padding: 2rem;
+    }
+
+    .code{
+        margin: 0;
+        font-size: 5rem;
+        font-weight: 700;
+        line-height: 1;
+        color: var(--color-primary);
+    }
+
+    h1{
+        margin: 0;
+        color: var(--color-text);
+        font-size: 1.5rem;
+        font-weight: 700;
+    }
+
+    .message{
+        margin: 0 0 0.5rem;
+        max-width: 320px;
+        color: var(--color-text-muted);
+        font-size: 1rem;
+    }
+</style>

+ 6 - 2
src/routes/+layout.svelte

@@ -20,13 +20,17 @@
 
     {#if page.url.pathname === "/"}
         {#if data.loggedIn === false}
-            <a href="/login" class="button">Log In</a>
+            <a href="/login" class="button">Sign In</a>
             <a href="/register" class="button">Sign Up</a>
         {/if}
     {/if}
 
     {#if page.url.pathname === "/register"}
-        <a href="/login" class="button">Log In</a>
+        <a href="/login" class="button">Sign In</a>
+    {/if}
+
+    {#if page.url.pathname === "/login"}
+        <a href="/register" class="button">Sign Up</a>
     {/if}
 
     <div class="notifications">

+ 34 - 3
src/routes/login/+page.server.js

@@ -1,7 +1,38 @@
+import {fail, redirect} from "@sveltejs/kit";
+import User from "$lib/server/models/User.js";
+import bcrypt from "bcrypt";
+import jwt from "jsonwebtoken";
+import {JWT_SECRET} from "$env/static/private";
+import {connectDB} from "$lib/server/db";
+
 export const actions = {
-    default: async ({request})=>{
+    default: async ({request, cookies})=>{
         const data = await request.formData();
-        const name = data.get("name");
-        const email
+        const email = data.get("email").toLowerCase();
+        const password = data.get("password");
+
+        await connectDB();
+
+        const user = await User.findOne({email: email});
+
+        if(!user) return fail(401, "Wrong email or password");
+        
+        if(!bcrypt.compare(password, user.password)) return fail(401, "Wrong email or password");
+
+        const tokenData = {
+            id: user._id,
+            token: user.token
+        };
+        const token = jwt.sign(tokenData, JWT_SECRET, {expiresIn: "30d"});
+
+        cookies.set("user", token, {
+            path: "/",
+            httpOnly: true,
+            secure: true,
+            sameSite: "strict",
+            maxAge: 60 * 60 *24 * 7 * 30
+        });
+
+        redirect(303, "/home")
     }
 }

+ 28 - 6
src/routes/login/+page.svelte

@@ -1,9 +1,31 @@
 <script>
-    const submit = ()=>{
-        console.log("submitting");
-    }
+    import {enhance} from "$app/forms";
+
+    let emailInput;
 </script>
 
-<form class="standardForm" onsubmit={submit}>
-    
-</form>
+<div class="container">
+    <form class="standardForm" method="POST" use:enhance>
+        <h1>Sign In</h1>
+
+        <label>Email
+            <input name="email" type="email" bind:this={emailInput} required>
+        </label>
+
+        <label>Password
+            <input name="password" type="password" required>
+        </label>
+
+        <button>Submit</button>
+    </form>
+</div>
+
+<style>
+    .container{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 100%;
+        height: calc(100% - 75px);
+    }
+</style>

+ 8 - 3
src/routes/register/+page.server.js

@@ -3,6 +3,7 @@ import validator from "validator";
 import bcrypt from "bcrypt";
 import {connectDB} from "$lib/server/db";
 import User from "$lib/server/models/User.js";
+import {randomUUID} from "crypto";
 
 export const actions = {
     default: async ({request})=>{
@@ -22,14 +23,18 @@ export const actions = {
 
         await connectDB();
 
+        const checkUser = await User.findOne({email: email});
+        if(checkUser) return fail(400, "User with this email already exists");
+
         const user = new User({
             name: name,
             email: email,
-            password: await bcrypt.hash(password, 12)
+            password: await bcrypt.hash(password, 12),
+            token: randomUUID()
         });
 
-        user.save();
+        await user.save();
 
-        redirect(303, "/home");
+        redirect(303, "/login");
     }
 }

+ 1 - 1
src/routes/register/+page.svelte

@@ -35,7 +35,7 @@
             <input name="confirmPassword" type="password" required>
         </label>
 
-        <button>Register</button>
+        <button>Submit</button>
     </form>
 </div>