Added local authentication (AFLOW-32)

This commit is contained in:
Leon Meier 2023-08-26 20:59:46 +02:00
parent 347979bb10
commit af896a6688
7 changed files with 183 additions and 14 deletions

View File

@ -5,6 +5,8 @@ import fileUpload from 'express-fileupload';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import * as eta from 'eta'; import * as eta from 'eta';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import session from 'express-session';
import passport from 'passport';
// Sentry // Sentry
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
@ -28,6 +30,7 @@ export const log = {
core: coreLogger, core: coreLogger,
db: coreLogger.scope('DB'), db: coreLogger.scope('DB'),
web: coreLogger.scope('WEB'), web: coreLogger.scope('WEB'),
auth: coreLogger.scope('AUTH'),
helper: coreLogger.scope('HELPER') helper: coreLogger.scope('HELPER')
}; };
@ -37,9 +40,22 @@ export const config = new ConfigHandler(__path + '/config.json', {
http_listen_address: '127.0.0.1', http_listen_address: '127.0.0.1',
http_port: 3000, http_port: 3000,
sentry_dsn: 'https://ID@sentry.example.com/PROJECTID', sentry_dsn: 'https://ID@sentry.example.com/PROJECTID',
debug: false debug: false,
auth: {
local: {
active: true,
users: {
user: 'password',
user1: 'password'
}
},
oidc: {
active: false
}
}
}); });
// TODO: Add errorhandling with some sort of message.
export const prisma = new PrismaClient({ export const prisma = new PrismaClient({
datasources: { datasources: {
db: { db: {
@ -67,16 +83,16 @@ Sentry.init({
environment: config.global.debug ? 'development' : 'production' environment: config.global.debug ? 'development' : 'production'
}); });
app.locals.versionRevLong = require('child_process') // TODO: Version check need to be rewritten.
.execSync('git rev-parse HEAD') app.locals.versionRevLong = require('child_process').execSync('git rev-parse HEAD').toString().trim();
.toString().trim() app.locals.versionRev = require('child_process').execSync('git rev-parse --short HEAD').toString().trim();
app.locals.versionRev = require('child_process') app.locals.versionRevLatest = require('child_process').execSync('git ls-remote --refs -q').toString().trim().split('\t')[0];
.execSync('git rev-parse --short HEAD')
.toString().trim() if (app.locals.versionRevLong === app.locals.versionRevLatest) {
app.locals.versionRevLatest = require('child_process') log.core.info(`Running Latest Version (${app.locals.versionRevLong})`);
.execSync('git ls-remote --refs -q') } else {
.toString().trim().split("\t")[0] log.core.info(`Running Version: ${app.locals.versionRevLong} (Latest: ${app.locals.versionRevLatest})`);
log.core.info(`Running revision ${app.locals.versionRevLong} (${app.locals.versionRevLatest} latest)`); }
// RequestHandler creates a separate execution context using domains, so that every // RequestHandler creates a separate execution context using domains, so that every
// transaction/span/breadcrumb is attached to its own Hub instance // transaction/span/breadcrumb is attached to its own Hub instance
@ -93,14 +109,23 @@ app.use(bodyParser.urlencoded({ extended: false }));
// Using bodyParser to parse JSON bodies into JS objects // Using bodyParser to parse JSON bodies into JS objects
app.use(bodyParser.json()); app.use(bodyParser.json());
// Session store
// TODO: Move secret to config -> Autogenerate.
app.use(
session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false,
cookie: { secure: false }
})
);
app.use(passport.authenticate('session'));
app.use(fileUpload()); app.use(fileUpload());
app.use(express.static(__path + '/static')); app.use(express.static(__path + '/static'));
app.use(routes); app.use(routes);
// The error handler must be before any other error middleware and after all controllers
app.use(Sentry.Handlers.errorHandler());
app.listen(config.global.http_port, config.global.http_listen_address, () => { app.listen(config.global.http_port, config.global.http_listen_address, () => {
log.web.info(`Listening at http://${config.global.http_listen_address}:${config.global.http_port}`); log.web.info(`Listening at http://${config.global.http_listen_address}:${config.global.http_port}`);
}); });

23
src/middleware/auth.mw.ts Normal file
View File

@ -0,0 +1,23 @@
/*
function checkAuthentication(req: any, res: any, next: Function) {
if (req.isAuthenticated()) {
//req.isAuthenticated() will return true if user is logged in
next();
} else {
res.redirect('/auth/login');
}
}
const checkIsInRole = (...roles) => (req, res, next) => {
if (!req.user) {
return res.redirect('/login')
}
const hasRole = roles.find(role => req.user.role === role)
if (!hasRole) {
return res.redirect('/login')
}
return next()
}
*/

View File

@ -1,4 +1,5 @@
import express from 'express'; import express from 'express';
import passport from 'passport';
// Route imports // Route imports
import testRoute from './test.js'; import testRoute from './test.js';

96
src/routes/auth/index.ts Normal file
View File

@ -0,0 +1,96 @@
import passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';
import express, { Request, Response } from 'express';
import { prisma, __path, log, config, app } from '../../index.js';
/* Configure password authentication strategy.
*
* The `LocalStrategy` authenticates users by verifying a username and password.
* The strategy parses the username and password from the request and calls the
* `verify` function.
*
* The `verify` function queries the database for the user record and verifies
* the password by hashing the password supplied by the user and comparing it to
* the hashed password stored in the database. If the comparison succeeds, the
* user is authenticated; otherwise, not.
*/
passport.use(
new LocalStrategy(function verify(username, password, cb) {
//log.auth.debug('LocalStrategy:', username, password);
for (const [user, pass] of Object.entries(config.global.auth.local.users)) {
//log.auth.debug('Loop(REQ):', username, password);
//log.auth.debug('Loop(CFG):', user, pass);
if (user === username && pass === password) {
log.auth.debug('LocalStrategy: success');
return cb(null, { username: username }); // This is the user object.
}
}
log.auth.debug('LocalStrategy: failed');
return cb(null, false, { message: 'Incorrect username or password.' });
})
/*
1. If the user not found in DB,
done (null, false)
2. If the user found in DB, but password does not match,
done (null, false)
3. If user found in DB and password match,
done (null, {authenticated_user})
*/
);
/* Configure session management.
*
* When a login session is established, information about the user will be
* stored in the session. This information is supplied by the `serializeUser`
* function, which is yielding the user ID and username.
*
* As the user interacts with the app, subsequent requests will be authenticated
* by verifying the session. The same user information that was serialized at
* session establishment will be restored when the session is authenticated by
* the `deserializeUser` function.
*
*/
passport.serializeUser(function (user: any, cb) {
process.nextTick(function () {
log.auth.debug('Called seriealizeUser');
log.auth.debug('user:', user);
return cb(null, {
username: user.username
});
});
});
passport.deserializeUser(function (user, cb) {
process.nextTick(function () {
log.auth.debug('Called deseriealizeUser');
return cb(null, user);
});
});
// Route imports
import testRoute from './test.js';
import loginRoute from './login.js';
//import logoutRoute from './login.js'
// Router base is '/auth'
const Router = express.Router({ strict: false });
Router.route('/login').get(loginRoute.get);
Router.route('/login').post(passport.authenticate('local', { successRedirect: '/', failureRedirect: '/auth/login?failed' }));
Router.route('/test').get(checkAuthentication, testRoute.get);
export default Router;
function checkAuthentication(req: Request, res: Response, next: Function) {
if (req.isAuthenticated()) {
//req.isAuthenticated() will return true if user is logged in
next();
} else {
res.redirect('/auth/login');
}
}

10
src/routes/auth/login.ts Normal file
View File

@ -0,0 +1,10 @@
import passport from 'passport';
import express, { Request, Response } from 'express';
import { prisma, __path, log } from '../../index.js';
function get(req: Request, res: Response) {
res.render(__path + '/src/frontend/auth/login.eta.html'); //, { items: items });
}
export default { get };

7
src/routes/auth/test.ts Normal file
View File

@ -0,0 +1,7 @@
import express, { Request, Response } from 'express';
function get(req: Request, res: Response) {
res.status(200).send('Auth Test Successful!');
};
export default { get };

View File

@ -1,17 +1,23 @@
import express, { Express } from 'express'; import express, { Express } from 'express';
import { __path, prisma } from '../index.js'; import { __path, prisma } from '../index.js';
import * as Sentry from '@sentry/node';
// Route imports // Route imports
import frontend_routes from './frontend/index.js'; import frontend_routes from './frontend/index.js';
import static_routes from './static/index.js'; import static_routes from './static/index.js';
import api_routes from './api/index.js'; import api_routes from './api/index.js';
import auth_routes from './auth/index.js';
const Router = express.Router({ strict: false }); const Router = express.Router({ strict: false });
Router.use('/static', static_routes); Router.use('/static', static_routes);
Router.use('/api', api_routes); Router.use('/api', api_routes);
Router.use('/auth', auth_routes);
Router.use('/', frontend_routes); Router.use('/', frontend_routes);
// The error handler must be before any other error middleware and after all controllers
Router.use(Sentry.Handlers.errorHandler());
// Default route. // Default route.
Router.all('*', function (req, res) { Router.all('*', function (req, res) {
// TODO: Respond based on content-type (with req.is('application/json')) // TODO: Respond based on content-type (with req.is('application/json'))
@ -23,3 +29,4 @@ Router.all('*', function (req, res) {
}); });
export default Router; export default Router;