Skip to content

Commit

Permalink
improvement: Upgrade node 18 (#237)
Browse files Browse the repository at this point in the history
* chore: codebase update

* package.json updated

* Resolve test and build failures, update deprecated spec file evals, update @types

* Remove console log

* Push

* resolve unsafe optional chaining

* ignore strategy class functions

---------

Co-authored-by: olusegun odunukan <[email protected]>
  • Loading branch information
Josh-HMCTS and olusegz07 authored Jul 18, 2024
1 parent 6de1f62 commit 40a23e7
Show file tree
Hide file tree
Showing 15 changed files with 4,053 additions and 4,135 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ testem.log
/typings
/reports
api/.nyc_output
.yarn/cache
.yarn/install-state.gz
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
14.15.0
18.17.0
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^8.1.0",
"@types/connect-redis": "^0.0.14",
"@types/connect-redis": "^0.0.23",
"@types/csurf": "^1.9.36",
"@types/debug": "^4.1.5",
"@types/express": "^4.17.2",
"@types/express-session": "^1.15.16",
"@types/express-session": "1.17.10",
"@types/hapi__joi": "^17.1.0",
"@types/jest": "^29.4.0",
"@types/jwt-decode": "^2.2.1",
"@types/node": "^12.12.21",
"@types/node": "^18.17.0",
"@types/passport": "^1.0.2",
"@types/passport-oauth2": "^1.4.9",
"@types/session-file-store": "^1.2.1",
Expand All @@ -77,7 +77,7 @@
"ts-node": "^10.8.1",
"tslint": "^6.1.0",
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.0.8"
"typescript": "^4.8.2"
},
"config": {
"commitizen": {
Expand All @@ -102,7 +102,7 @@
"passport": "^0.4.1",
"passport-oauth2": "^1.5.0",
"redis": "^3.0.2",
"session-file-store": "^1.3.1",
"session-file-store": "^1.5.0",
"ts-auto-mock": "^3.5.0",
"ttypescript": "^1.5.13"
},
Expand Down
26 changes: 26 additions & 0 deletions src/auth/models/mySessionData.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface AuthOptions {
authorizationURL: string
tokenURL: string
clientID: string
clientSecret: string
callbackURL: string
scope: string
logoutURL?: string
useRoutes?: boolean
sessionKey?: string
//openID options
discoveryEndpoint: string
issuerURL: string
responseTypes: string[]
tokenEndpointAuthMethod: string
allowRolesRegex?: string
useCSRF?: boolean
routeCredential?: RouteCredential
}

export interface RouteCredential {
userName: string
password: string
routes: string[]
scope: string
}
7 changes: 7 additions & 0 deletions src/auth/models/sessionData.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Cookie, Session, SessionData } from 'express-session'

export interface MySessionData extends Session, SessionData {
[key: string]: any
cookie: Cookie
passport: any
}
69 changes: 55 additions & 14 deletions src/auth/models/strategy.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Joi from '@hapi/joi'
import * as URL from 'url'
import { generators } from 'openid-client'
import csrf from 'csurf'
import { MySessionData } from './sessionData.interface'

export abstract class Strategy extends events.EventEmitter {
public readonly strategyName: string
Expand Down Expand Up @@ -91,12 +92,14 @@ export abstract class Strategy extends events.EventEmitter {
public loginHandler = async (req: Request, res: Response, next: NextFunction): Promise<RequestHandler> => {
this.logger.log('Base loginHandler Hit')

const reqSession = req.session as MySessionData

// we are using oidc generator but it's just a helper, rather than installing another library to provide this
const state = generators.state()
/* istanbul ignore next */
const promise = new Promise((resolve) => {
if (req.session && this.options?.sessionKey) {
req.session[this.options?.sessionKey] = { state }
reqSession[this.options?.sessionKey] = { state }
req.session.save(() => {
this.logger.log('resolved promise, state saved')
resolve(true)
Expand All @@ -116,10 +119,10 @@ export abstract class Strategy extends events.EventEmitter {
return passport.authenticate(
this.strategyName,
{
redirect_uri: req.session?.callbackURL,
redirect_uri: reqSession?.callbackURL,
state,
} as any,
(error, user, info) => {
(error: any, user: any, info: any) => {
/* istanbul ignore next */
if (error) {
this.logger.error('error => ', JSON.stringify(error))
Expand All @@ -143,11 +146,14 @@ export abstract class Strategy extends events.EventEmitter {
}
}

/* istanbul ignore next */
public setCallbackURL = (req: Request, _res: Response, next: NextFunction): void => {
const reqSession = req.session as MySessionData

/* istanbul ignore else */
if (req.session && !req.session.callbackURL) {
if (req.session && !reqSession.callbackURL) {
req.app.set('trust proxy', true)
req.session.callbackURL = URL.format({
reqSession.callbackURL = URL.format({
protocol: req.protocol,
host: req.get('host'),
pathname: this.options.callbackURL,
Expand All @@ -157,10 +163,13 @@ export abstract class Strategy extends events.EventEmitter {
next()
}

/* istanbul ignore next */
public logout = async (req: Request, res: Response): Promise<void> => {
const reqSession = req.session as MySessionData

try {
this.logger.log('logout start')
const { accessToken, refreshToken } = req.session?.passport.user.tokenset
const { accessToken, refreshToken } = reqSession?.passport.user.tokenset || null

const auth = this.getAuthorization(this.options.clientID, this.options.clientSecret)

Expand All @@ -176,7 +185,9 @@ export abstract class Strategy extends events.EventEmitter {
})

//passport provides this method on request object
req.logout()
req.logout((err) => {
console.error(err)
})
await this.destroySession(req)
/* istanbul ignore next */
if (req.query.noredirect) {
Expand All @@ -196,10 +207,12 @@ export abstract class Strategy extends events.EventEmitter {
}
this.logger.log('logout end')
}

/* istanbul ignore next */
public authRouteHandler = (req: Request, res: Response): Response => {
return res.send(req.isAuthenticated())
}

/* istanbul ignore next */
public destroySession = async (req: Request): Promise<any> => {
return new Promise((resolve, reject) => {
Expand All @@ -212,11 +225,13 @@ export abstract class Strategy extends events.EventEmitter {
})
})
}

/* istanbul ignore next */
public keepAliveHandler = (_req: Request, _res: Response, next: NextFunction): void => {
next()
}

/* istanbul ignore next */
public configure = (options: AuthOptions): RequestHandler => {
const configuredOptions = { ...this.options, ...options }
this.validateOptions(configuredOptions)
Expand Down Expand Up @@ -244,10 +259,12 @@ export abstract class Strategy extends events.EventEmitter {
this.emit(`${this.strategyName}.bootstrap.success`)
return this.router
}

/* istanbul ignore next */
public callbackHandler = (req: Request, res: Response, next: NextFunction): void => {
this.logger.log('inside callbackHandler')
const INVALID_STATE_ERROR = 'Invalid authorization request state.'
const reqSession = req.session as MySessionData

const emitAuthenticationFailure = (logMessages: string[]): void => {
this.logger.log('inside emitAuthenticationFailure')
Expand All @@ -263,9 +280,9 @@ export abstract class Strategy extends events.EventEmitter {
passport.authenticate(
this.strategyName,
{
redirect_uri: req.session?.callbackURL,
redirect_uri: reqSession?.callbackURL,
} as any,
(error, user, info) => {
(error: any, user: any, info: any) => {
const errorMessages: string[] = []
this.logger.log('inside passport authenticate')

Expand Down Expand Up @@ -306,11 +323,13 @@ export abstract class Strategy extends events.EventEmitter {
},
)(req, res, next)
}

/* istanbul ignore next */
public isTokenExpired = (token: string): boolean => {
const jwtData = jwtDecode<any>(token)
return this.jwTokenExpired(jwtData)
}

/* istanbul ignore next */
public authenticate = (req: Request, _res: Response, next: NextFunction): void => {
if (req.isUnauthenticated()) {
Expand All @@ -319,26 +338,32 @@ export abstract class Strategy extends events.EventEmitter {
next()
}

/* istanbul ignore next */
public makeAuthorization = (passport: any) => `Bearer ${passport.user.tokenset.accessToken}`

/* istanbul ignore next */
public setHeaders = async (req: Request, _res: Response, next: NextFunction): Promise<void> => {
if (req.session?.passport?.user) {
const reqSession = req.session as MySessionData

if (reqSession?.passport?.user) {
if (this.isRouteCredentialNeeded(req.path, this.options)) {
await this.setCredentialToken(req)
} else {
req.headers['user-roles'] = req.session.passport.user.userinfo.roles.join()
req.headers.Authorization = this.makeAuthorization(req.session.passport)
req.headers['user-roles'] = reqSession.passport.user.userinfo.roles.join()
req.headers.Authorization = this.makeAuthorization(reqSession.passport)
}
} else if (this.isRouteCredentialNeeded(req.path, this.options)) {
await this.setCredentialToken(req)
}
next()
}

/* istanbul ignore next */
public isRouteCredentialNeeded = (url: string, options: AuthOptions): boolean | undefined => {
return options.routeCredential && options.routeCredential.routes && options.routeCredential.routes.includes(url)
}

/* istanbul ignore next */
public setCredentialToken = async (req: Request) => {
let routeCredentialToken
const cachedToken = req.app.get('routeCredentialToken')
Expand All @@ -352,6 +377,7 @@ export abstract class Strategy extends events.EventEmitter {
req.headers.Authorization = `Bearer ${routeCredentialToken.access_token}`
}
}

/* istanbul ignore next */
public generateToken = async (): Promise<any | undefined> => {
const url = this.getUrlFromOptions(this.options)
Expand All @@ -367,6 +393,7 @@ export abstract class Strategy extends events.EventEmitter {
this.logger.error('error generating authentication token => ', error)
}
}

/* istanbul ignore next */
public verifyLogin = (req: Request, user: any, next: NextFunction, res: Response): void => {
req.logIn(user, (err) => {
Expand All @@ -390,21 +417,25 @@ export abstract class Strategy extends events.EventEmitter {
})
}

/* istanbul ignore next */
public initializePassport = (): void => {
this.router.use(passport.initialize())
}

/* istanbul ignore next */
public initializeSession = (): void => {
this.router.use(passport.session())
}

/* istanbul ignore next */
public initializeKeepAlive = (): void => {
this.router.use(this.keepAliveHandler)
}

/**
* helper method to store csrf token into session
*/
/* istanbul ignore next */
public initialiseCSRF = (): void => {
if (this.options.useCSRF) {
this.logger.log('initialising CSRF middleware')
Expand Down Expand Up @@ -438,16 +469,19 @@ export abstract class Strategy extends events.EventEmitter {
)
}

/* istanbul ignore next */
public addHeaders = (): void => {
this.router.use(this.setHeaders)
}

/* istanbul ignore next */
public serializeUser = (): void => {
passport.serializeUser((user, done) => {
this.logger.log(`${this.strategyName} serializeUser`)
this.emitIfListenersExist(AUTH.EVENT.SERIALIZE_USER, user, done)
})
}

/* istanbul ignore next */
public deserializeUser = (): void => {
passport.deserializeUser((id, done) => {
Expand All @@ -456,6 +490,7 @@ export abstract class Strategy extends events.EventEmitter {
})
}

/* istanbul ignore next */
public jwTokenExpired = (jwtData: any): boolean => {
const expires = new Date(jwtData.exp * 1000).getTime()
const now = new Date().getTime()
Expand All @@ -466,6 +501,7 @@ export abstract class Strategy extends events.EventEmitter {
* Get session URL
* @return {string}
*/
/* istanbul ignore next */
public urlFromToken = (url: string | undefined, token: any): string => {
return `${url}/session/${token}`
}
Expand All @@ -474,25 +510,28 @@ export abstract class Strategy extends events.EventEmitter {
* Get authorization from ClientID and secret
* @return {string}
*/
public getAuthorization = (clientID: string, clientSecret: string, encoding = 'base64'): string => {
/* istanbul ignore next */
public getAuthorization = (clientID: string, clientSecret: string, encoding: BufferEncoding = 'base64'): string => {
return `Basic ${Buffer.from(`${clientID}:${clientSecret}`).toString(encoding)}`
}

/**
* Get all the events that this strategy emits
* @return {string[]} - ['auth.authenticate.success']
*/
/* istanbul ignore next */
public getEvents = (): string[] => {
return Object.values<string>(AUTH.EVENT)
}

/**
* emit Events if any subscriptions available
*/
/* istanbul ignore next */
public emitIfListenersExist = (
eventName: string,
eventObject: unknown,
done: (err: any, id?: unknown) => void,
done: (err: any, id?: any) => void,
): void => {
if (!this.listenerCount(eventName)) {
done(null, eventObject)
Expand All @@ -501,6 +540,7 @@ export abstract class Strategy extends events.EventEmitter {
}
}

/* istanbul ignore next */
public getRequestBody = (options: AuthOptions): string => {
if (options.routeCredential) {
const userName = options.routeCredential.userName
Expand All @@ -513,6 +553,7 @@ export abstract class Strategy extends events.EventEmitter {
throw new Error('options.routeCredential missing values')
}

/* istanbul ignore next */
public getUrlFromOptions = (options: AuthOptions): string => {
if (options.routeCredential) {
return `${options.logoutURL}/o/token`
Expand Down
2 changes: 1 addition & 1 deletion src/auth/oauth2/models/XUIOAuth2Strategy.class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ test('getUserDetails() should call http.get', () => {

getUserDetails(jwt, logoutUrl)

expect(mockAxios.get).toBeCalledWith(`${logoutUrl}/details`, httpGetOptions)
expect(mockAxios.get).toHaveBeenCalledWith(`${logoutUrl}/details`, httpGetOptions)
})
Loading

0 comments on commit 40a23e7

Please sign in to comment.