فهرست منبع

Oh shit i should commit

Khysnik 1 ماه پیش
کامیت
666ea0e6ce
6فایلهای تغییر یافته به همراه290 افزوده شده و 0 حذف شده
  1. 5 0
      .gitignore
  2. 44 0
      db/database.js
  3. 155 0
      routes/auth/auth.js
  4. 37 0
      routes/auth/mail.js
  5. 6 0
      routes/index.js
  6. 43 0
      server.js

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+frontend/
+node_modules/
+package-lock.json
+db/*.db
+.env

+ 44 - 0
db/database.js

@@ -0,0 +1,44 @@
+import Datastore from "@seald-io/nedb";
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+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'),
+                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();
+    }
+
+    async get(name) {
+        return this.database[name].find({});
+    }
+
+    async add(name, doc) {
+        return this.database[name].insert(doc);
+    }
+
+    async remove(name, id) {
+        return this.database[name].remove({ _id: id });
+    }
+
+    async find(name, query) {
+        return this.database[name].findOne(query);
+    }
+}

+ 155 - 0
routes/auth/auth.js

@@ -0,0 +1,155 @@
+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
+    })
+}

+ 37 - 0
routes/auth/mail.js

@@ -0,0 +1,37 @@
+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",
+            },
+        });
+        return this;
+    }
+
+    async send(target, subject, message){
+        try{
+            this.transport.sendMail({
+                from: 'noreply@xd4y.zip',
+                to: target,
+                subject: subject,
+                text: message
+            })
+        } catch(e) {
+            return false
+        }
+        return true
+    }
+
+}

+ 6 - 0
routes/index.js

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

+ 43 - 0
server.js

@@ -0,0 +1,43 @@
+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();
+
+const UserDB = new DB('users');
+UserDB.init();
+
+const app = express();
+
+app.use(
+    cors({
+        origin: ['http://localhost:5173', 'http://localhost:5174'],
+        credentials: true,
+    })
+);
+
+app.use(cookieParser());
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+
+app.locals.db = UserDB;
+
+app.use('/api', routes);
+
+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)
+ */