Skip to content

Commit

Permalink
Updated D365 integration.
Browse files Browse the repository at this point in the history
Code cleanup.
  • Loading branch information
weskubo-cgi authored and weskubo-cgi committed Oct 6, 2023
1 parent 8093f63 commit f17e354
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 275 deletions.
7 changes: 4 additions & 3 deletions backend/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": true,
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
"printWidth": 200,
"trailingComma": "all",
"endOfLine": "auto"
}
8 changes: 4 additions & 4 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ccof-backend",
"name": "ofm-backend",
"version": "0.0.1",
"description": "The backend portion of the CCOF web app",
"description": "The backend portion of the OFM web app",
"scripts": {
"serve": "npm run setlocal && npm run servecmd",
"serve-linux": "export NODE_ENV='local' && nodemon ./src/server.js",
Expand Down Expand Up @@ -167,9 +167,9 @@
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/bcgov/EDUC-CCOF.git"
"url": "git+https://github.com/bcgov/ecc-ofm.git"
},
"bugs": {
"url": "https://github.com/bcgov/EDUC-CCOF/issues"
"url": "https://github.com/bcgov/ecc-ofm/issues"
}
}
309 changes: 162 additions & 147 deletions backend/src/app.js
Original file line number Diff line number Diff line change
@@ -1,201 +1,216 @@
'use strict';

const config = require('./config/index');
const dotenv = require('dotenv');
const log = require('./components/logger');
const morgan = require('morgan');
const session = require('express-session');
const express = require('express');
const atob = require('atob');
const passport = require('passport');
const helmet = require('helmet');
const cors = require('cors');
const utils = require('./components/utils');
const auth = require('./components/auth');
const bodyParser = require('body-parser');
dotenv.config();

const JWTStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const OidcStrategy = require('passport-openidconnect-keycloak-idp').Strategy;
const noCache = require('nocache');
const apiRouter = express.Router();
const authRouter = require('./routes/auth');
const userRouter = require('./routes/user');
const configRouter = require('./routes/config');


const organizationRouter = require('./routes/organization');

const connectRedis = require('connect-redis');
const promMid = require('express-prometheus-middleware');
'use strict'

const config = require('./config/index')
const dotenv = require('dotenv')
const log = require('./components/logger')
const morgan = require('morgan')
const session = require('express-session')
const express = require('express')
const atob = require('atob')
const passport = require('passport')
const helmet = require('helmet')
const cors = require('cors')
const utils = require('./components/utils')
const auth = require('./components/auth')
const bodyParser = require('body-parser')
dotenv.config()

const JWTStrategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt
const OidcStrategy = require('passport-openidconnect-keycloak-idp').Strategy
const noCache = require('nocache')
const apiRouter = express.Router()
const authRouter = require('./routes/auth')
const userRouter = require('./routes/user')
const configRouter = require('./routes/config')

const organizationRouter = require('./routes/organization')

const connectRedis = require('connect-redis')
const promMid = require('express-prometheus-middleware')

//initialize app
const app = express();
app.set('trust proxy', 1);
const app = express()
app.set('trust proxy', 1)
//sets security measures (headers, etc)
app.use(cors());
app.use(helmet());
app.use(noCache());
app.use(promMid({
metricsPath: '/api/prometheus',
collectDefaultMetrics: true,
requestDurationBuckets: [0.1, 0.5, 1, 1.5]
}));
app.use(cors())
app.use(helmet())
app.use(noCache())
app.use(
promMid({
metricsPath: '/api/prometheus',
collectDefaultMetrics: true,
requestDurationBuckets: [0.1, 0.5, 1, 1.5],
}),
)
//tells the app to use json as means of transporting data
app.use(bodyParser.json({ limit: '50mb', extended: true }));
app.use(bodyParser.urlencoded({
extended: true,
limit: '50mb'
}));
app.use(bodyParser.json({ limit: '50mb', extended: true }))
app.use(
bodyParser.urlencoded({
extended: true,
limit: '50mb',
}),
)

const logStream = {
write: (message) => {
log.info(message);
}
};
log.info(message)
},
}

const dbSession = getRedisDbSession();
const dbSession = getRedisDbSession()
const cookie = {
secure: true,
httpOnly: true,
maxAge: 1800000 //30 minutes in ms. this is same as session time. DO NOT MODIFY, IF MODIFIED, MAKE SURE SAME AS SESSION TIME OUT VALUE.
};
maxAge: 1800000, //30 minutes in ms. this is same as session time. DO NOT MODIFY, IF MODIFIED, MAKE SURE SAME AS SESSION TIME OUT VALUE.
}
if ('local' === config.get('environment')) {
cookie.secure = false;
cookie.secure = false
}
//sets cookies for security purposes (prevent cookie access, allow secure connections only, etc)
app.use(session({
name: 'ccof_cookie',
secret: config.get('oidc:clientSecret'),
resave: false,
saveUninitialized: true,
cookie: cookie,
store: dbSession
}));

app.use(require('./routes/health-check').router);
app.use(
session({
name: 'ofm_cookie',
secret: config.get('oidc:clientSecret'),
resave: false,
saveUninitialized: true,
cookie: cookie,
store: dbSession,
}),
)

app.use(require('./routes/health-check').router)
//initialize routing and session. Cookies are now only reachable via requests (not js)
app.use(passport.initialize());
app.use(passport.session());
app.use(passport.initialize())
app.use(passport.session())

function getRedisDbSession() {
if (config.get('redis:use') == 'true') {
const Redis = require('./util/redis/redis-client');
Redis.init(); // call the init to initialize appropriate client, and reuse it across the app.
const RedisStore = connectRedis(session);
const Redis = require('./util/redis/redis-client')
Redis.init() // call the init to initialize appropriate client, and reuse it across the app.
const RedisStore = connectRedis(session)
const dbSession = new RedisStore({
client: Redis.getRedisClient(),
prefix: 'ccof-sess:',
});
return dbSession;
})
return dbSession
}
return undefined;
return undefined
}

function addLoginPassportUse(discovery, strategyName, callbackURI, kc_idp_hint, clientId, clientSecret) {
passport.use(strategyName, new OidcStrategy({
issuer: discovery.issuer,
authorizationURL: discovery.authorization_endpoint,
tokenURL: discovery.token_endpoint,
userInfoURL: discovery.userinfo_endpoint,
clientID: config.get(clientId),
clientSecret: config.get(clientSecret),
callbackURL: callbackURI,
scope: 'openid',
kc_idp_hint: kc_idp_hint
}, (_issuer, profile, _context, _idToken, accessToken, refreshToken, done) => {
if ((typeof (accessToken) === 'undefined') || (accessToken === null) ||
(typeof (refreshToken) === 'undefined') || (refreshToken === null)) {
return done('No access token', null);
}

//set access and refresh tokens
profile.jwtFrontend = auth.generateUiToken();
profile.jwt = accessToken;
profile._json = parseJwt(accessToken);
profile.refreshToken = refreshToken;
return done(null, profile);
}));
passport.use(
strategyName,
new OidcStrategy(
{
issuer: discovery.issuer,
authorizationURL: discovery.authorization_endpoint,
tokenURL: discovery.token_endpoint,
userInfoURL: discovery.userinfo_endpoint,
clientID: config.get(clientId),
clientSecret: config.get(clientSecret),
callbackURL: callbackURI,
scope: 'openid',
kc_idp_hint: kc_idp_hint,
},
(_issuer, profile, _context, _idToken, accessToken, refreshToken, done) => {
if (typeof accessToken === 'undefined' || accessToken === null || typeof refreshToken === 'undefined' || refreshToken === null) {
return done('No access token', null)
}

//set access and refresh tokens
profile.jwtFrontend = auth.generateUiToken()
profile.jwt = accessToken
profile._json = parseJwt(accessToken)
profile.refreshToken = refreshToken
return done(null, profile)
},
),
)
}

const parseJwt = (token) => {
try {
return JSON.parse(atob(token.split('.')[1]));
return JSON.parse(atob(token.split('.')[1]))
} catch (e) {
return null;
return null
}
};
}
//initialize our authentication strategy
utils.getOidcDiscovery().then(discovery => {
utils.getOidcDiscovery().then((discovery) => {
//OIDC Strategy is used for authorization
addLoginPassportUse(discovery, 'oidcIdir', config.get('server:frontend') + '/api/auth/callback_idir', 'keycloak_bcdevexchange_idir', 'oidc:clientIdIDIR', 'oidc:clientSecretIDIR');
addLoginPassportUse(discovery, 'oidcBceid', config.get('server:frontend') + '/api/auth/callback', 'keycloak_bcdevexchange_bceid', 'oidc:clientId', 'oidc:clientSecret');
addLoginPassportUse(discovery, 'oidcIdir', config.get('server:frontend') + '/api/auth/callback_idir', 'keycloak_bcdevexchange_idir', 'oidc:clientIdIDIR', 'oidc:clientSecretIDIR')
addLoginPassportUse(discovery, 'oidcBceid', config.get('server:frontend') + '/api/auth/callback', 'keycloak_bcdevexchange_bceid', 'oidc:clientId', 'oidc:clientSecret')

//JWT strategy is used for authorization keycloak_bcdevexchange_idir
passport.use('jwt', new JWTStrategy({
algorithms: ['RS256'],
// Keycloak 7.3.0 no longer automatically supplies matching client_id audience.
// If audience checking is needed, check the following SO to update Keycloak first.
// Ref: https://stackoverflow.com/a/53627747
audience: config.get('server:frontend'),
issuer: config.get('tokenGenerate:issuer'),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get('tokenGenerate:publicKey'),
ignoreExpiration: true
}, (jwtPayload, done) => {
if ((typeof (jwtPayload) === 'undefined') || (jwtPayload === null)) {
return done('No JWT token', null);
}

done(null, {
email: jwtPayload.email,
familyName: jwtPayload.family_name,
givenName: jwtPayload.given_name,
jwt: jwtPayload,
name: jwtPayload.name,
user_guid: jwtPayload.user_guid,
realmRole: jwtPayload.realm_role
});
}));
});
passport.use(
'jwt',
new JWTStrategy(
{
algorithms: ['RS256'],
// Keycloak 7.3.0 no longer automatically supplies matching client_id audience.
// If audience checking is needed, check the following SO to update Keycloak first.
// Ref: https://stackoverflow.com/a/53627747
audience: config.get('server:frontend'),
issuer: config.get('tokenGenerate:issuer'),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get('tokenGenerate:publicKey'),
ignoreExpiration: true,
},
(jwtPayload, done) => {
if (typeof jwtPayload === 'undefined' || jwtPayload === null) {
return done('No JWT token', null)
}

done(null, {
email: jwtPayload.email,
familyName: jwtPayload.family_name,
givenName: jwtPayload.given_name,
jwt: jwtPayload,
name: jwtPayload.name,
user_guid: jwtPayload.user_guid,
realmRole: jwtPayload.realm_role,
})
},
),
)
})
//functions for serializing/deserializing users
passport.serializeUser((user, next) => next(null, user));
passport.deserializeUser((obj, next) => next(null, obj));

passport.serializeUser((user, next) => next(null, user))
passport.deserializeUser((obj, next) => next(null, obj))

app.use(morgan(config.get('server:morganFormat'), { 'stream': logStream }));
app.use(morgan(config.get('server:morganFormat'), { stream: logStream }))
//set up routing to auth and main API
app.use(/(\/api)?/, apiRouter);
app.use(/(\/api)?/, apiRouter)

apiRouter.use('/auth', authRouter);
apiRouter.use('/user', userRouter);
apiRouter.use('/config',configRouter);
apiRouter.use('/auth', authRouter)
apiRouter.use('/user', userRouter)
apiRouter.use('/config', configRouter)

//TODO: Router from MyCCS only here for 1st backend iteration example. To be removed ...
apiRouter.use('/organization', organizationRouter);
apiRouter.use('/organization', organizationRouter)

//Handle 500 error
app.use((err, _req, res, next) => {
//This is from the ResultValidation
if (err.errors && err.mapped) {
return res.status(400).json({
errors: err.mapped()
});
errors: err.mapped(),
})
}
log.error(err?.stack);
res?.redirect(config?.get('server:frontend') + '/error?message=500_internal_error');
next();
});
log.error(err?.stack)
res?.redirect(config?.get('server:frontend') + '/error?message=500_internal_error')
next()
})

// Handle 404 error
app.use((_req, res) => {
log.error('404 Error');
res.redirect(config?.get('server:frontend') + '/error?message=404_Page_Not_Found');
});
log.error('404 Error')
res.redirect(config?.get('server:frontend') + '/error?message=404_Page_Not_Found')
})

// Prevent unhandled errors from crashing application
process.on('unhandledRejection', (err) => {
log.error('Unhandled Rejection at:', err?.stack || err);
});
module.exports = app;
log.error('Unhandled Rejection at:', err?.stack || err)
})
module.exports = app
Loading

0 comments on commit f17e354

Please sign in to comment.