浏览代码

Clean architecture attempt

Khysnik 1 月之前
父节点
当前提交
d137711f07
共有 8 个文件被更改,包括 177 次插入195 次删除
  1. 1 1
      .gitignore
  2. 0 155
      routes/auth/auth.js
  3. 0 6
      routes/index.js
  4. 1 6
      src/adapters/database.js
  5. 17 0
      src/adapters/jwt.js
  6. 3 9
      src/adapters/mail.js
  7. 149 0
      src/routes/auth.js
  8. 6 18
      src/server.js

+ 1 - 1
.gitignore

@@ -1,4 +1,4 @@
-frontend/
+src/webui/
 node_modules/
 package-lock.json
 db/*.db

+ 0 - 155
routes/auth/auth.js

@@ -1,155 +0,0 @@
-import express from 'express';
-import jwt from 'jsonwebtoken';
-import argon2 from 'argon2';
-import Mailer from "./mail.js"
-import * as crypto from "node:crypto";
-
-export const auth = express.Router();
-
-const mailer = new Mailer().init()
-
-let mfaCodes = []
-
-auth.get('/user', async (req, res) => {
-    const UserDB = req.app.locals.db;
-    const token = req.cookies.token;
-
-    if (!token) {
-        return res.status(401).json({error: 'No token'});
-    }
-
-    let decoded;
-    try {
-        decoded = jwt.verify(token, process.env.JWT_SECRET);
-    } catch {
-        res.clearCookie('token', {
-            httpOnly: true,
-            secure: false,
-            sameSite: 'lax',
-            path: '/'
-        });
-        return res.status(401).json({error: 'Invalid token'});
-    }
-
-    const row = await UserDB.find('users', {_id: decoded.id});
-
-    if (!row) {
-        res.clearCookie('token', {
-            httpOnly: true,
-            secure: false,
-            sameSite: 'lax',
-            path: '/'
-        });
-        return res.status(401).json({error: 'Invalid token'});
-    }
-
-    const {password, ...safeRow} = row;
-    res.json(safeRow);
-});
-
-auth.get('/login/:data', async (req, res) => {
-    const UserDB = req.app.locals.db;
-
-    if (!req.params.data) {
-        return res.status(401).send('Invalid token');
-    }
-
-    try {
-        const data = JSON.parse(atob(req.params.data));
-
-        const row = await UserDB.find('users', {_id: data.id});
-        if (!row) {
-            return res.status(401).send('Invalid token');
-        }
-
-        console.log(mfaCodes)
-        const mfaRow = mfaCodes.find(obj => String(obj.id) === String(data.id));
-        if (!mfaRow) {
-            return res.status(401).send('Invalid token');
-        }
-
-        if (mfaRow.token !== data.token) {
-            return res.status(401).send('Invalid token');
-        }
-
-        const token = jwt.sign(
-            {id: row._id, username: row.username},
-            process.env.JWT_SECRET,
-            {expiresIn: '1h'}
-        );
-
-        res.cookie('token', token, {
-            httpOnly: true,
-            secure: false,
-            sameSite: 'lax',
-            maxAge: 60 * 60 * 1000,
-            path: '/'
-        });
-
-        const index = mfaCodes.findIndex(obj => String(obj.id) === String(data.id));
-        if (index !== -1) {
-            mfaCodes.splice(index, 1);
-        }
-
-        res.redirect('http://localhost:5173/');
-    } catch (e) {
-        console.log(e);
-        res.status(401).send('Invalid token');
-    }
-});
-
-
-auth.post('/login', async (req, res) => {
-    let UserDB = req.app.locals.db;
-
-    const {username, password} = req.body;
-    if (!username || !password) {
-        return res.status(400).json({error: 'Missing credentials'});
-    }
-
-    const row = await UserDB.find('users', {username});
-    if (!row) return res.status(401).json({error: 'User doesnt exist'});
-
-    if (!await argon2.verify(row.password, password)) return res.status(401).json({error: 'Invalid password'});
-
-    let mfgen = crypto.randomBytes(64).toString('hex');
-
-    const mfaToken = {id: row._id, token: mfgen}
-
-
-    mfaCodes.push(mfaToken)
-
-    console.log(mfaCodes)
-
-    await mailer.send(row.email, "2FA Login Link", `Hey, ${row.username}!\n\nYour login link is: http://localhost:3000/api/auth/login/${btoa(JSON.stringify(mfaToken))}`)
-
-    res.json({success: true, user: {username}});
-});
-
-auth.post('/register', async (req, res) => {
-    let UserDB = req.app.locals.db;
-    const {email, password} = req.body;
-
-    const username = email.split("@")[0]
-
-    //await mailer.send(email, "signup request", "this worked?")
-
-    if (await UserDB.find('users', {username: username})) return res.status(400).json({error: 'User already exists'});
-
-    const hash = await hashPass(password)
-
-    UserDB.add('users', {username: username, email: email, password: hash})
-
-    res.json({success: true, user: {username}});
-
-})
-
-async function hashPass(input) {
-    return await argon2.hash(input, {
-        type: argon2.argon2id,
-        salt: Buffer.from(process.env.SALT, 'utf8'),
-        timeCost: 2,
-        memoryCost: 19456,
-        parallelism: 1
-    })
-}

+ 0 - 6
routes/index.js

@@ -1,6 +0,0 @@
-import express from 'express';
-export const routes = express.Router();
-
-import {auth} from './auth/auth.js';
-
-routes.use('/auth', auth)

+ 1 - 6
db/database.js → src/adapters/database.js

@@ -8,20 +8,15 @@ const __dirname = path.dirname(__filename);
 export default class DB {
     constructor(dbName) {
         this.dbName = dbName;
-    }
-
-    init() {
         this.database = {
             users: new Datastore({
-                filename: path.join(__dirname, this.dbName.toLowerCase()+'.db'),
+                filename: path.join(__dirname, "..", "../db", this.dbName.toLowerCase()+'.db'),
                 autoload: true
             })
         };
     }
 
     async count(name) {
-        // Return the count as a Promise to align with typical async patterns
-        // If your collection API uses callbacks, you can adapt accordingly.
         const collection = this.database[name];
         return collection.count();
     }

+ 17 - 0
src/adapters/jwt.js

@@ -0,0 +1,17 @@
+import jwt from "jsonwebtoken";
+
+export default class Jwt {
+    constructor(secret) {
+        this.secret = secret;
+    }
+
+    create(payload, exp) {
+        return jwt.sign(payload, this.secret, {
+            expiresIn: exp
+        });
+    }
+
+    verify(token) {
+        return jwt.verify(token, this.secret);
+    }
+}

+ 3 - 9
routes/auth/mail.js → src/adapters/mail.js

@@ -1,20 +1,14 @@
 import nodemailer from 'nodemailer'
 
 export default class Mailer {
-    constructor() {
-
-    }
-
     init(){
         this.transport = nodemailer.createTransport({
             host: "mail.privateemail.com",
             port: 587,
             secure: false,
             auth: {
-                //user: process.env.MAIL_USER,
-                //pass: process.env.MAIL_PASS,
-                user: "khysnik@xd4y.zip",
-                pass: "Amicusaquavita@7533",
+                user: process.env.MAIL_USER,
+                pass: process.env.MAIL_PASS,
             },
         });
         return this;
@@ -22,7 +16,7 @@ export default class Mailer {
 
     async send(target, subject, message){
         try{
-            this.transport.sendMail({
+            await this.transport.sendMail({
                 from: 'noreply@xd4y.zip',
                 to: target,
                 subject: subject,

+ 149 - 0
src/routes/auth.js

@@ -0,0 +1,149 @@
+import express from 'express';
+import argon2 from 'argon2';
+import * as crypto from "node:crypto";
+
+import Jwt from "../adapters/jwt.js";
+import Mailer from "../adapters/mail.js"
+
+const mailer = new Mailer().init()
+
+const jwt = new Jwt(process.env.JWT_SECRET)
+
+let mfaCodes = []
+
+export default function authRoutes(UserDB) {
+
+    const auth = express.Router();
+
+    auth.get('/user', async (req, res) => {
+        const token = req.cookies.token;
+
+        if (!token) {
+            return res.status(401).json({error: 'No token'});
+        }
+
+        let decoded;
+        try {
+            decoded = jwt.verify(token, process.env.JWT_SECRET);
+        } catch {
+            res.clearCookie('token', {
+                httpOnly: true,
+                secure: false,
+                sameSite: 'lax',
+                path: '/'
+            });
+            return res.status(401).json({error: 'Invalid token'});
+        }
+
+        const row = await UserDB.find('users', {_id: decoded.id});
+
+        if (!row) {
+            res.clearCookie('token', {
+                httpOnly: true,
+                secure: false,
+                sameSite: 'lax',
+                path: '/'
+            });
+            return res.status(401).json({error: 'Invalid token'});
+        }
+
+        const {password, ...safeRow} = row;
+        res.json(safeRow);
+    });
+
+    auth.get('/login/:data', async (req, res) => {
+        if (!req.params.data) {
+            return res.status(401).send('Invalid token');
+        }
+
+        try {
+            const data = JSON.parse(atob(req.params.data));
+
+            const row = await UserDB.find('users', {_id: data.id});
+            if (!row) {
+                return res.status(401).send('Invalid token');
+            }
+
+            const mfaRow = mfaCodes.find(obj => String(obj.id) === String(data.id));
+            if (!mfaRow) {
+                return res.status(401).send('Invalid token');
+            }
+
+            if (mfaRow.token !== data.token) {
+                return res.status(401).send('Invalid token');
+            }
+
+            const token = jwt.create({id: row._id, username: row.username}, '1h');
+
+            res.cookie('token', token, {
+                httpOnly: true,
+                secure: false,
+                sameSite: 'lax',
+                maxAge: 60 * 60 * 1000,
+                path: '/'
+            });
+
+            const index = mfaCodes.findIndex(obj => String(obj.id) === String(data.id));
+            if (index !== -1) {
+                mfaCodes.splice(index, 1);
+            }
+
+            res.redirect('http://localhost:5173/');
+        } catch (e) {
+            console.log(e);
+            res.status(401).send('Invalid token');
+        }
+    });
+
+    auth.post('/login', async (req, res) => {
+        const {username, password} = req.body;
+        if (!username || !password) {
+            return res.status(400).json({error: 'Missing credentials'});
+        }
+
+        const row = await UserDB.find('users', {username});
+        if (!row) return res.status(401).json({error: 'User doesnt exist'});
+
+        if (!await argon2.verify(row.password, password)) return res.status(401).json({error: 'Invalid password'});
+
+        let mfgen = crypto.randomBytes(64).toString('hex');
+
+        const mfaToken = {id: row._id, token: mfgen}
+
+        mfaCodes.push(mfaToken)
+
+        await mailer.send(row.email, "2FA Login Link", `Hey, ${row.username}!\n\nYour login link is: http://localhost:3000/api/auth/login/${btoa(JSON.stringify(mfaToken))}`)
+
+        res.json({success: true, user: {username}});
+    });
+
+    auth.post('/register', async (req, res) => {
+        const {email, password} = req.body;
+
+        const username = email.split("@")[0]
+
+        //await mailer.send(email, "signup request", "this worked?")
+
+        if (await UserDB.find('users', {username: username})) return res.status(400).json({error: 'User already exists'});
+
+        const hash = await hashPass(password)
+
+        UserDB.add('users', {username: username, email: email, password: hash})
+
+        res.json({success: true, user: {username}});
+
+    })
+
+    return auth
+
+}
+
+async function hashPass(input) {
+    return await argon2.hash(input, {
+        type: argon2.argon2id,
+        salt: Buffer.from(process.env.SALT, 'utf8'),
+        timeCost: 2,
+        memoryCost: 19456,
+        parallelism: 1
+    })
+}

+ 6 - 18
server.js → src/server.js

@@ -1,14 +1,13 @@
+import 'dotenv/config';
+
 import express from 'express';
 import cors from 'cors';
 import cookieParser from 'cookie-parser';
-import dotenv from 'dotenv';
-import DB from './db/database.js';
-import { routes } from './routes/index.js';
 
-dotenv.config();
+import DB from './adapters/database.js';
+import authRoutes from './routes/auth.js';
 
 const UserDB = new DB('users');
-UserDB.init();
 
 const app = express();
 
@@ -23,21 +22,10 @@ app.use(cookieParser());
 app.use(express.json());
 app.use(express.urlencoded({ extended: true }));
 
-app.locals.db = UserDB;
-
-app.use('/api', routes);
+app.use('/api/auth', authRoutes(UserDB));
 
 app.listen(3000, () => console.log('Server on :3000'));
 
 // POST /api/auth/register - {email, password}
 // POST /api/auth/login - {username, password}
-// GET /api/auth/user
-
-/*
-TODO
-permissions
-2fa
-logout
-jwt expiration/renewal
-password strength (DONE)
- */
+// GET /api/auth/user