diff --git a/api.planx.uk/modules/auth/controller.ts b/api.planx.uk/modules/auth/controller.ts index 784a914456..8728f49b5d 100644 --- a/api.planx.uk/modules/auth/controller.ts +++ b/api.planx.uk/modules/auth/controller.ts @@ -1,7 +1,7 @@ import { CookieOptions, RequestHandler, Response } from "express"; import { Request } from "express-jwt"; -import { microsoftOidcClient } from './passport' +import { microsoftOidcClient } from "./passport"; export const failedLogin: RequestHandler = (_req, _res, next) => next({ diff --git a/api.planx.uk/modules/auth/middleware.ts b/api.planx.uk/modules/auth/middleware.ts index 31e5639f9a..b190da7fe5 100644 --- a/api.planx.uk/modules/auth/middleware.ts +++ b/api.planx.uk/modules/auth/middleware.ts @@ -4,7 +4,7 @@ import { expressjwt } from "express-jwt"; import { RequestHandler } from "http-proxy-middleware"; import { AsyncLocalStorage } from "async_hooks"; import { Request } from "express"; -import { generators } from 'openid-client' +import { generators } from "openid-client"; import { Role } from "@opensystemslab/planx-core/types"; @@ -130,8 +130,8 @@ export const useMicrosoftAuth: RequestHandler = (req, res, next) => { // generate a nonce to enable us to validate the response from OP const nonce = generators.nonce(); console.debug(`Generated a nonce: %s`, nonce); - req.session!.nonce = nonce - + req.session!.nonce = nonce; + // @ts-expect-error (method not typed to accept nonce, but it does pass it to the strategy) return passportWithStrategies.authenticate("microsoft-oidc", { prompt: "select_account", diff --git a/api.planx.uk/modules/auth/passport.ts b/api.planx.uk/modules/auth/passport.ts index 295c69b2ae..d2815b1fb4 100644 --- a/api.planx.uk/modules/auth/passport.ts +++ b/api.planx.uk/modules/auth/passport.ts @@ -1,40 +1,47 @@ -import { custom, Issuer } from 'openid-client'; -import passport from 'passport'; +import { custom, Issuer } from "openid-client"; +import passport from "passport"; -import { googleStrategy } from './strategy/google'; -import { getMicrosoftOidcStrategy, getMicrosoftClientConfig, MICROSOFT_OPENID_CONFIG_URL } from './strategy/microsoft-oidc'; +import { googleStrategy } from "./strategy/google"; +import { + getMicrosoftOidcStrategy, + getMicrosoftClientConfig, + MICROSOFT_OPENID_CONFIG_URL, +} from "./strategy/microsoft-oidc"; const setupPassport = () => { // TODO: remove below config (timeout extended for local testing with poor connection) custom.setHttpOptionsDefaults({ timeout: 10000, - }) + }); // explicitly instantiate new passport for clarity - let passportWithStrategies = new passport.Passport() + const passportWithStrategies = new passport.Passport(); // build Microsoft OIDC client, and use it to build the related strategy let microsoftOidcClient; - Issuer.discover(MICROSOFT_OPENID_CONFIG_URL).then(microsoftIssuer => { + Issuer.discover(MICROSOFT_OPENID_CONFIG_URL).then((microsoftIssuer) => { console.debug("Discovered issuer %s", microsoftIssuer.issuer); const microsoftClientConfig = getMicrosoftClientConfig(); microsoftOidcClient = new microsoftIssuer.Client(microsoftClientConfig); console.debug("Built Microsoft client: %O", microsoftOidcClient); - passportWithStrategies.use('microsoft-oidc', getMicrosoftOidcStrategy(microsoftOidcClient)); + passportWithStrategies.use( + "microsoft-oidc", + getMicrosoftOidcStrategy(microsoftOidcClient), + ); }); // do any other aspects of passport setup which can be handled here - passportWithStrategies.use('google', googleStrategy); + passportWithStrategies.use("google", googleStrategy); passportWithStrategies.serializeUser((user: any, done) => { done(null, user); }); passportWithStrategies.deserializeUser((obj: any, done) => { done(null, obj); }); - - return { passportWithStrategies, microsoftOidcClient } -} + + return { passportWithStrategies, microsoftOidcClient }; +}; // instantiate and export the new passport class and Microsoft client as early as possible -let { passportWithStrategies, microsoftOidcClient } = setupPassport(); -export { passportWithStrategies, microsoftOidcClient }; \ No newline at end of file +const { passportWithStrategies, microsoftOidcClient } = setupPassport(); +export { passportWithStrategies, microsoftOidcClient }; diff --git a/api.planx.uk/modules/auth/routes.ts b/api.planx.uk/modules/auth/routes.ts index ec94f6209b..6d4b212b0e 100644 --- a/api.planx.uk/modules/auth/routes.ts +++ b/api.planx.uk/modules/auth/routes.ts @@ -12,7 +12,7 @@ router.get( Middleware.useGoogleCallbackAuth, Controller.handleSuccess, ); -router.get("/auth/microsoft", Middleware.useMicrosoftAuth) +router.get("/auth/microsoft", Middleware.useMicrosoftAuth); router.post( "/auth/microsoft/callback", Middleware.useMicrosoftCallbackAuth, diff --git a/api.planx.uk/modules/auth/strategy/microsoft-oidc.ts b/api.planx.uk/modules/auth/strategy/microsoft-oidc.ts index 3cf5958576..b30fab45e4 100644 --- a/api.planx.uk/modules/auth/strategy/microsoft-oidc.ts +++ b/api.planx.uk/modules/auth/strategy/microsoft-oidc.ts @@ -1,12 +1,13 @@ import { Strategy, TokenSet, Client, ClientMetadata } from "openid-client"; import { buildJWT } from "../service"; -export const MICROSOFT_OPENID_CONFIG_URL = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"; +export const MICROSOFT_OPENID_CONFIG_URL = + "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"; export const getMicrosoftClientConfig = (): ClientMetadata => { const client_id = process.env.MICROSOFT_CLIENT_ID!; - if (typeof client_id !== 'string') { - throw new Error('No MICROSOFT_CLIENT_ID in the environment'); + if (typeof client_id !== "string") { + throw new Error("No MICROSOFT_CLIENT_ID in the environment"); } return { client_id, @@ -14,14 +15,13 @@ export const getMicrosoftClientConfig = (): ClientMetadata => { redirect_uris: [`${process.env.API_URL_EXT}/auth/microsoft/callback`], post_logout_redirect_uris: [process.env.EDITOR_URL_EXT!], response_types: ["id_token"], - } -} + }; +}; // oidc = OpenID Connect, an auth standard built on top of OAuth 2.0 -export const getMicrosoftOidcStrategy = ( - client: Client, -): Strategy => { - return new Strategy({ +export const getMicrosoftOidcStrategy = (client: Client): Strategy => { + return new Strategy( + { client: client, params: { scope: "openid email profile", @@ -41,20 +41,26 @@ export const getMicrosoftOidcStrategy = ( const login_hint = claims.login_hint; if (returned_nonce != req.session.nonce) { - return done(new Error("Returned nonce does not match session nonce"), null) - }; + return done( + new Error("Returned nonce does not match session nonce"), + null, + ); + } if (!email) { - return done (new Error("Unable to authenticate without email"), null) - }; + return done(new Error("Unable to authenticate without email"), null); + } const jwt = await buildJWT(email); if (!jwt) { - return done({ - status: 404, - message: `User (${email}) not found. Do you need to log in to a different Microsoft Account?`, - } as any, null); + return done( + { + status: 404, + message: `User (${email}) not found. Do you need to log in to a different Microsoft Account?`, + } as any, + null, + ); } return done(null, { jwt });