diff --git a/.gitignore b/.gitignore index 19b87ef..3ac2215 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ sas/ tmp/ build/ .env +security.json \ No newline at end of file diff --git a/security.json.example b/security.json.example new file mode 100644 index 0000000..8b834dc --- /dev/null +++ b/security.json.example @@ -0,0 +1,3 @@ +{ + "code": "place security code here" +} diff --git a/src/app.ts b/src/app.ts index 004b6af..d47b681 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,10 +1,16 @@ import path from 'path' import express from 'express' import { renderFile } from 'ejs' +import dotenv from 'dotenv' import { Routes } from './routes' import { passportMiddleware } from './middleware' import { getAuthMechanisms } from './utils' +import { AuthMechanism } from './types' +import session from 'express-session' + +dotenv.config() +const authMechanisms = getAuthMechanisms() const app = express() @@ -16,14 +22,44 @@ app.set('views', path.join(__dirname, './views')) app.use(express.json({ limit: '50mb' })) app.use(express.static(path.join(__dirname, '..', 'public'))) +const sessionConfig = { + secret: 'keyboard cat', + resave: false, + saveUninitialized: false, + cookie: { + secure: false // set this to true on production + } +} + +if (app.get('env') === 'production') { + app.set('trust proxy', 1) // trust first proxy + sessionConfig.cookie.secure = true // serve secure cookies +} + +app.use(session(sessionConfig)) + +app.get(Routes.Login, (req, res) => { + if ( + authMechanisms.length === 1 && + authMechanisms[0] === AuthMechanism.NoSecurity + ) { + res.redirect('/') + } else { + const session: any = req.session + const isAuthenticated = !!session?.passport?.user + + if (isAuthenticated) { + res.redirect('/') + } else { + res.render('sasjslogon.html', { authMechanisms }) + } + } +}) + app.use(passportMiddleware()) -const authMechanisms = getAuthMechanisms() -app.get(Routes.Login, (req, res) => { - res.render('sasjslogon.html', { authMechanisms }) -}) app.get('/error', (req, res) => res.redirect('/500.html')) app.get('/unauthorized', (req, res) => res.redirect('/401.html')) -app.get('*', (req, res) => res.status(404).redirect('/404.html')) +// app.get('*', (req, res) => res.status(404).redirect('/404.html')) export default app diff --git a/src/middleware.ts b/src/middleware.ts index a87e908..0cf3b05 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,37 +1,21 @@ +import path from 'path' import jwt from 'jsonwebtoken' -import dotenv from 'dotenv' import express from 'express' -import session from 'express-session' import passport from 'passport' import { Strategy } from 'passport-local' const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2') import { ensureLoggedIn } from 'connect-ensure-login' +import { readFile } from '@sasjs/utils' + import { AuthMechanism } from './types' import { getAuthMechanisms } from './utils' import indexRouter, { Routes } from './routes' export const passportMiddleware = (): express.Express => { - dotenv.config() const authMechanisms = getAuthMechanisms() const middleware = express() - const sessionConfig = { - secret: 'keyboard cat', - resave: false, - saveUninitialized: false, - cookie: { - secure: false // set this to true on production - } - } - - if (middleware.get('env') === 'production') { - middleware.set('trust proxy', 1) // trust first proxy - sessionConfig.cookie.secure = true // serve secure cookies - } - - middleware.use(session(sessionConfig)) - setupPassportStrategies(authMechanisms) middleware.use(passport.initialize()) @@ -49,10 +33,10 @@ export const passportMiddleware = (): express.Express => { authMechanisms[0] === AuthMechanism.NoSecurity ) { console.log('Using No Security') - middleware.get('/', indexRouter) + middleware.all('/*', indexRouter) } else { - middleware.get( - '/', + middleware.all( + '/*', ensureLoggedIn({ redirectTo: '/SASjsLogon' }), indexRouter ) @@ -65,15 +49,18 @@ const setupPassportStrategies = (authMechanisms: string[]) => { if (authMechanisms.includes(AuthMechanism.Local)) { console.log('Using Local Authentication') passport.use( - new Strategy((username: string, password: string, cb: Function) => { - console.log('username', username) - if (username !== 'SaadJutt') - return cb(null, false, { message: 'Incorrect Username' }) + new Strategy(async (username: string, code: string, cb: Function) => { + const content = await readFile( + path.join(__dirname, '..', 'security.json') + ) + const { code: securityCode } = JSON.parse(content) + if (securityCode !== code) + return cb(null, false, { message: 'Incorrect Security Code' }) const user = { id: 'SOMEID', username: username, - displayName: 'displayName' + displayName: username } return cb(null, user) }) @@ -95,11 +82,20 @@ const setupPassportStrategies = (authMechanisms: string[]) => { profile: any, done: any ) { - // currently we can't find a way to exchange access token by user info (see userProfile implementation), so - // you will need a jwt-package like https://github.com/auth0/node-jsonwebtoken to decode id_token and get waad profile - var waadProfile = profile || jwt.decode(params.id_token) + const decoded = jwt.decode(params.id_token) - done(null, { id: waadProfile.upn }) + const user = { + id: 'ID', + username: 'username', + displayName: 'display name' + } + if (decoded && typeof decoded === 'object') { + user.id = decoded.oid + user.username = decoded.unique_name + user.displayName = decoded.name + } + + done(null, user) } ) ) @@ -127,20 +123,30 @@ const setupPassportRoutes = ( app.get( Routes.AzureSignInRedirect, passport.authenticate('azure_ad_oauth2', { - successRedirect: '/', failureRedirect: Routes.Login, failureMessage: true - }) + }), + (req, res) => { + const session: any = req.session + const returnTo = session.returnTo ?? '/' + session.returnTo = undefined + res.redirect(returnTo) + } ) } if (authMechanisms.includes(AuthMechanism.Local)) { app.post( Routes.LocalSignIn, - passport.authenticate(['local'], { - successRedirect: '/', + passport.authenticate('local', { failureRedirect: Routes.Login, failureMessage: true - }) + }), + (req, res) => { + const session: any = req.session + const returnTo = session.returnTo ?? '/' + session.returnTo = undefined + res.redirect(returnTo) + } ) } } diff --git a/src/routes/index.ts b/src/routes/index.ts index 1a20533..504c2cb 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,23 +1,40 @@ import express from 'express' import { processSas, createFileTree, getTreeExample } from '../controllers' -import { ExecutionResult, isRequestQuery, isFileTree } from '../types' +import { + ExecutionResult, + isRequestQuery, + isFileTree, + AuthMechanism +} from '../types' +import { getAuthMechanisms } from '../utils' const router = express.Router() +const header = (user: any) => { + const authMechanisms = getAuthMechanisms() + if ( + authMechanisms.length === 1 && + authMechanisms[0] === AuthMechanism.NoSecurity + ) + return '
No Security applied
Logged in as ${user.username} Logout
Log is located:
${result.logPath}Log:
`) +Log:
`) }) router.post('/deploy', async (req, res) => { diff --git a/src/views/sasjslogon.html b/src/views/sasjslogon.html index cd43725..df64ea6 100644 --- a/src/views/sasjslogon.html +++ b/src/views/sasjslogon.html @@ -27,7 +27,11 @@