Source

tools/hooks/useOidc.ts

import {useReactOidc} from "@axa-fr/react-oidc-context";
import {useLocation} from "react-router-dom";
import {setPostLoginPath} from "../LoginRedirectionHandler";
import {useJcuIdentity} from "../../services";
import { useCallback, useMemo } from "react";

/**
 * @typedef {object} OidcHookReturn
 */
type OidcHookReturn = {
    /**
     * Function to trigger an OIDC log in
     */
    login: () => void,
    /**
     * Function to trigger an OIDC log out
     */
    logout: () => void,
    /**
     * An OIDC user object
     */
    user: any,
    /**
     * Whether there is a currently logged in user
     */
    loggedIn: boolean,
    /**
     * OIDC event object
     */
    events: any
}

/**
 * Custom hook for interacting with OIDC services.
 *
 * All of the return values are wrappers to the axa
 * [useReactOidc](https://github.com/AxaGuilDEv/react-oidc/tree/master/packages/context#how-to-consume--hooks-method-pagesdashboarddashboardjs) hook.
 *
 * The primary differences are:
 *  - `oidcUser` has been renamed to `user` for easier reference
 *  - the `login` function has additional functionality baked in (automatic recording of pre-login URL)
 *  - new `loggedIn` key to quickly determine if a user is logged in
 *
 *  Usage is the same as other hooks:
 *
 *  ```
 *  import {useOidc} from '@jcu/spark'
 *
 *  ...
 *
 *  const {login, logout, user, loggedIn} = useOidc()
 *  ```
 * @category Hooks
 *
 * @return {OidcHookReturn} A pair of functions for logging in/out and the current OIDC user
 */
export function useOidc(): OidcHookReturn {
    const {login: oidcLogin, logout: oidcLogout, oidcUser, events} = useReactOidc()
    const currentLocation = useLocation()
    const introspectedUser = useJcuIdentity()

    // Memoize the login and logout callbacks to prevent unnecessary downstream rerenders
    const login = useCallback(() => {
        if (currentLocation) {
            setPostLoginPath(currentLocation.pathname)
        }
        return oidcLogin()
    }, [oidcLogin, currentLocation])

    const logout = useCallback(() => {
        return oidcLogout()
    }, [oidcLogout])

    // Memoize the user object so only actual changes trigger downstream rerenders
    const user = useMemo(() => {
        let user = null
        if (introspectedUser && oidcUser) {
            user = mergeUserData(oidcUser, introspectedUser)
        }
        return user
    }, [oidcUser, introspectedUser])

    return {login, logout, user, loggedIn: !!user, events}
}

// -------------------------------------------------------------
//TODO: JSDOC?
function mergeUserData(oidc, introspect) {
    // Extract the scopes from the introspected data
    const {scopes} = introspect

    // Use the OIDC data as a default (populate what we know already)
    // Override with anything we gain from introspection (more authoritative)
    let mergedUser = {
        ...oidc,
        scope: scopes,
    }
    return mergedUser
}