Source

services/AxaOidcProvider.tsx

import React, {useContext, useEffect, useState} from 'react'
import {AuthenticationProvider, useReactOidc} from '@axa-fr/react-oidc-context';
import {ConfigContext} from "./ConfigManager";
import {ApiManager} from "./ApiManager";
import {Loading, NeedLogin, NoAccess} from "../components/extras";

type oidcConfiguration = {
    client_id: string,
    redirect_uri: string,
    response_type: string,
    scope: string,
    authority: string,
    silent_redirect_uri: string,
    automaticSilentRenew?: boolean,
    loadUserInfo?: boolean,
    post_logout_redirect_uri?: string,
    metadata?: oidcMetadata,
    filterProtocolClaims?: boolean,
}

type oidcMetadata = {
    issuer?: string,
    jwks_uri?: string,
    authorization_endpoint?: string,
    token_endpoint?: string,
    userinfo_endpoint?: string,
    end_session_endpoint?: string,
    revocation_endpoint?: string,
    introspection_endpoint?: string,
}

type providerProps = {
    notAuthenticated?: React.Component, // react component displayed during authentication
    notAuthorized?: React.Component, // react component displayed in case user is not Authorised
    authenticating?: React.Component, // react component displayed when about to redirect user to be authenticated
    callbackComponentOverride?: React.Component, // react component displayed when user is connected
    sessionLostComponent?: React.Component,
    configuration: oidcConfiguration,
    isEnabled?: boolean,
    loggerLevel?: number
}

// List of oidc configuration keys that will need to be rebased
const REBASEABLE_URLS = [
    'redirect_uri',
    'silent_redirect_uri',
    'post_logout_redirect_uri'
]

/**
 * Used to set up generic URLs (e.g. /callback) into their app specific equivalent
 * (e.g. https://apps.jcu.edu.au/password-selfservice/callback)
 * @ignore
 *
 * @param oidcConfig The OIDC config containing the generic urls
 * @param baseUrl The base URL for the SPA
 *
 * @returns An object containing the oidc with updated rebase-able values
 */
function rebaseUrls(oidcConfig: oidcConfiguration, baseUrl) {
    let newConfig = {...oidcConfig}
    for (let key of REBASEABLE_URLS) {
        if (newConfig[key] && newConfig[key].indexOf('/') === 0) {
            newConfig[key] = baseUrl + newConfig[key]
        }
    }
    return newConfig
}

// JCU defaults using SIT SSO and testapp ID
const DEFAULT_OIDC_CONFIG: oidcConfiguration = {
    authority: 'https://sit-sso.jcu.edu.au/openam/oauth2/jcu',
    silent_redirect_uri: '/silent_callback',
    client_id: 'testapp',
    redirect_uri: '/redircallback',
    post_logout_redirect_uri: '/',
    response_type: 'code',
    scope: 'openid profile email',
    filterProtocolClaims: true,
    loadUserInfo: true,
    automaticSilentRenew: true
}

/**
 *
 * @component
 * @category Context Providers
 */
function AxaOidcProvider(props) {
    // Load the application configuration
    const appConfigContext = useContext(ConfigContext)

    const [oidcConfig, setOidcConfig] = useState<oidcConfiguration | null>(null)

    useEffect(() => {
        if (appConfigContext) {
            let requestedScopes = []
            // Loop through all the APIs registered in the API manager, get their scopes, and
            // template them if required (to get the correct scope for this environment)
            for (let i of ApiManager.interfacesToLoad) {
                let scopes = Object.values(i.scopes)
                if (scopes) {
                    for (let scope of scopes) {
                        //@ts-ignore
                        requestedScopes.push(ApiManager.getTemplatedScope(scope as string, appConfigContext.env))
                    }
                }
            }

            // Combine all the scopes into an OIDC scope string
            let scopeString = requestedScopes.join(' ')

            // Determine the base URL for this application, to ensure all callback routes go to the correct application
            let baseUrl =
                `${window.location.protocol}//${window.location.hostname}${
                    window.location.port ? `:${window.location.port}` : ''
                }` + appConfigContext.basePath

            // Merge the default OIDC configuration and the app specific configuration.
            // App specific overrides default
            let oidcConfig = {...DEFAULT_OIDC_CONFIG, ...appConfigContext.authentication}

            // Attach the API scopes to the base list provided
            oidcConfig.scope = oidcConfig.scope.trim() + ' ' + scopeString

            // Convert any generic OIDC callbacks (e.g. /callback) into their app specific equivalent
            // (e.g. https://localhost:3000/callback)
            oidcConfig = rebaseUrls(oidcConfig, baseUrl)

            // Save the OIDC configuration into state
            setOidcConfig(oidcConfig)
        }
        // When component unmounts, set OIDC config back to null
        return () => setOidcConfig(null)
    }, [appConfigContext])

    // If there's no config loading the provider will crash, instead render null
    if (!oidcConfig) {
        return null
    } else {
        return (
            <AuthenticationProvider
                // @ts-ignore
                configuration={oidcConfig}
                // @ts-ignore
                notAuthenticated={NeedLogin}
                authenticating={Loading}
                callbackComponentOverride={Loading}
                notAuthorized={NoAccess}
                //loggerLevel={oidcLog.DEBUG}
            >
                {props.children}
            </AuthenticationProvider>
        )
    }

}

export default AxaOidcProvider

export {useReactOidc, AxaOidcProvider}