From e5ad7961b3179ad299d6c2179031c4fd3882802f Mon Sep 17 00:00:00 2001 From: andrzej Date: Fri, 7 Jun 2024 14:09:26 +0200 Subject: [PATCH] implement basic auth flow --- src/auth/auth.mts | 71 ++++++++++++++++++++++++++++++++++++ src/db.mts | 28 ++++++++++++++ src/index.mts | 13 +++++-- src/model/model.mts | 17 +++++++++ src/routes/routes.mts | 50 +++++++++++++++++++++++++ src/routes/secure-routes.mts | 15 ++++++++ 6 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 src/auth/auth.mts create mode 100644 src/db.mts create mode 100644 src/model/model.mts create mode 100644 src/routes/routes.mts create mode 100644 src/routes/secure-routes.mts diff --git a/src/auth/auth.mts b/src/auth/auth.mts new file mode 100644 index 0000000..7819609 --- /dev/null +++ b/src/auth/auth.mts @@ -0,0 +1,71 @@ +import passport from 'passport' +import { Strategy as localStrategy } from 'passport-local' +import { User, encryptPwd, pwdIsValid } from '../model/model.mjs' +import { Strategy as JWTstrategy, ExtractJwt } from 'passport-jwt' +import { userDb } from '../db.mjs' + +passport.use('signup', new localStrategy( + { + usernameField: 'username', + passwordField: 'password' + }, + async (username, password, done) => { + console.log("signup auth strategy has begun") + try { + const encryptedPwd = await encryptPwd(password) + const user = await userDb("users").insert({ username: username, password: encryptedPwd }).returning(["username", "password"]) + console.log(`user: ${user}`) + return done(null, user) + } catch (err) { + console.error(err) + done(err) + } + })) + +passport.use('login', + new localStrategy( + { + usernameField: "username", + passwordField: "password", + session: false + }, + async (email, password, done) => { + console.log("local strategy called") + try { + let returnedUser: Array = await userDb("users").select("username", "password").where({ username: email }) + const user: User = returnedUser[0] + console.log(`user: ${user}`) + if (!user || returnedUser.length === 0) { + return done(null, false, { message: "user not found" }) + } + + const validate: boolean = await pwdIsValid(password, user) + console.log(`isValidPassword? ${validate}`) + + if (!validate) { + return done(null, false, { message: "wrong password" }) + } + + return done(null, user, { message: "logged in successfully" }) + } catch (error) { + return done(error) + } + } + ) +) + +passport.use( + new JWTstrategy( + { + secretOrKey: "TOP_SECRET", + jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('secret_token') + }, + async (token, done) => { + try { + return done(null, token.user) + } catch (error) { + done(error) + } + } + ) +) diff --git a/src/db.mts b/src/db.mts new file mode 100644 index 0000000..9d54b89 --- /dev/null +++ b/src/db.mts @@ -0,0 +1,28 @@ +import knex from "knex"; + +export const db = knex({ + client: 'sqlite3', + connection: { + filename: "./submissions" + }, + useNullAsDefault: true +}) + +export const testDb = knex({ + client: 'sqlite3', + connection: { + filename: "./test.db" + }, + useNullAsDefault: true +}) + +export const userDb = knex({ + client: "sqlite3", + connection: { + filename: "./users" + }, + useNullAsDefault: true +}) + + + diff --git a/src/index.mts b/src/index.mts index 31d1421..ab3c3e1 100644 --- a/src/index.mts +++ b/src/index.mts @@ -1,10 +1,15 @@ +import bodyParser from 'body-parser'; import express from 'express'; +import passport from 'passport'; +import { default as nonSecureRoutes } from "./routes/routes.mjs" +import { default as secureRoutes } from "./routes/secure-routes.mjs" +import "./auth/auth.mjs" const app = express(); const port = 3000; - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); +app.use(passport.initialize()) +app.use(bodyParser.json()) +app.use("/", nonSecureRoutes) +app.use("/app", secureRoutes) app.listen(port, () => { return console.log(`Express is listening at http://localhost:${port}`); diff --git a/src/model/model.mts b/src/model/model.mts new file mode 100644 index 0000000..3b8456a --- /dev/null +++ b/src/model/model.mts @@ -0,0 +1,17 @@ +import bcrypt from "bcrypt" + +export interface User { + username: string; + password: string; + _id?: number; +} + +export async function encryptPwd(pwd: string) { + return Promise.resolve(bcrypt.hash(pwd, 10)) +} + +export async function pwdIsValid(pwd: string, user: User): Promise { + return Promise.resolve(bcrypt.compare(pwd, user.password)) +} + + diff --git a/src/routes/routes.mts b/src/routes/routes.mts new file mode 100644 index 0000000..2378485 --- /dev/null +++ b/src/routes/routes.mts @@ -0,0 +1,50 @@ +import express from "express" +import passport from "passport" +import jwt from 'jsonwebtoken'; +import { User } from "../model/model.mjs"; + +const router = express.Router() + +router.post("/signup", + passport.authenticate("signup", { session: false }), + async (req, res, next) => { + res.json({ + message: "signup successful", + user: req.user + }) + }) + +router.post( + '/login', + async (req, res, next) => { + passport.authenticate( + 'login', + async (err: Error, user: User, _: any) => { + try { + if (err || !user) { + const error = new Error('An error occurred.'); + + return next(error); + } + + req.login( + user, + { session: false }, + async (error) => { + if (error) return next(error); + + const body = { _id: user._id, username: user.username }; + const token = jwt.sign({ user: body }, 'TOP_SECRET', { expiresIn: "20m" }); + + return res.json({ token }); + } + ); + } catch (error) { + return next(error); + } + } + )(req, res, next); + } +); + +export default router diff --git a/src/routes/secure-routes.mts b/src/routes/secure-routes.mts new file mode 100644 index 0000000..e224c7b --- /dev/null +++ b/src/routes/secure-routes.mts @@ -0,0 +1,15 @@ +import express from "express" +const router = express.Router() + +router.get( + '/profile', + (req, res, next) => { + res.json({ + message: "you made it to the secure route", + user: req.user, + token: req.query.secret_token, + }) + } +) + +export default router