UNPKG

@slashid/docusaurus-theme-slashid

Version:
185 lines (155 loc) 4.86 kB
/* ============================================================================ * Copyright (c) SlashID * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * ========================================================================== */ import type { ContextType } from "react"; import type { PropSidebarItem, PropSidebarItemHtml, PropSidebarItemCategory, } from "@docusaurus/plugin-content-docs"; import { ConfigurationContext } from "@slashid/react"; import type { User } from "@slashid/slashid"; import { OAuthProvider } from "@slashid/slashid"; import globToRegexp from "glob-to-regexp"; export interface PrivatePath { path: string | RegExp; groups?: string[]; } export interface ThemeConfig { slashID: LegacyThemeConfig | NewThemeConfig; } export type UXMode = "redirect" | "modal"; export interface LegacyThemeConfig { orgID: string; // @deprecated use formConfiguration instead oidcClientID?: string; // @deprecated use formConfiguration instead oidcProvider?: OAuthProvider; forceLogin?: boolean; baseURL?: string; sdkURL?: string; privatePaths?: PrivatePath[]; // TODO make it configurable to show login instead of redirect privateRedirectPath?: string; } export type SlashIDConfigurationProviderConfig = Partial< ContextType<typeof ConfigurationContext> >; export interface NewThemeConfig { orgID: string; forceLogin?: boolean; baseURL?: string; sdkURL?: string; // behaviour when accessing a private path uxMode?: UXMode; privatePaths?: PrivatePath[]; privateRedirectPath?: string; formConfiguration?: SlashIDConfigurationProviderConfig; } export type SlashIDProps = { auth: boolean; groups?: string[]; }; export function shouldPathRender( path: string | undefined, privatePaths: PrivatePath[] | undefined, user: User | undefined ) { if (!privatePaths || !path) return true; const matchingPrivatePaths = privatePaths.filter((privatePath) => { let matches = false; if (typeof privatePath.path === "string") { matches = path.includes(privatePath.path); } else if (privatePath.path instanceof RegExp) { matches = privatePath.path.test(path); } if (!matches) return false; if (!user) return true; if (privatePath.groups) { const userGroups = user.getGroups(); return !privatePath.groups.every((group: string) => userGroups.includes(group) ); } // invalid config - should render return false; }); return matchingPrivatePaths.length === 0; } /** * Tests if the given item should be rendered based on the user state and the front matter config. * @param slashIDProps set by the front matter on the relevant page * @param user SlashID User instance * @returns a {boolean} indicating whether the user should be able to see the item */ export function shouldItemRender( slashIDProps: SlashIDProps | undefined, user: User | undefined ) { // paths take precedence if (!slashIDProps || !slashIDProps.auth) { return true; } const { groups } = slashIDProps; if (!user) { return false; } if (groups) { const userGroups = user.getGroups(); return groups.every((group: string) => userGroups.includes(group)); } return true; } export function isCategory( item: PropSidebarItem ): item is PropSidebarItemCategory { return item.hasOwnProperty("items") && item.type === "category"; } export function isHtmlSidebarItem( item: PropSidebarItem ): item is PropSidebarItemHtml { return item.type === "html"; } export function shouldNoItemsRender( items: PropSidebarItem[], user: User | undefined, getSlashIDProps: (item: PropSidebarItem) => SlashIDProps | undefined ): boolean { return ( items.filter((item) => { if (isCategory(item)) { return shouldNoItemsRender(item.items, user, getSlashIDProps); } else { // custom items should always be rendered as they might have custom rules if (isHtmlSidebarItem(item)) return true; return shouldItemRender(getSlashIDProps(item), user); } }).length === 0 ); } /** * Ensure the privatePaths config is converted to RegExp instances if globs are used. */ export function convertGlobToRegex( config: ThemeConfig["slashID"] ): ThemeConfig["slashID"] { if (config?.privatePaths) { let convertedPaths = []; convertedPaths = config.privatePaths.map((pathConfig) => { if (typeof pathConfig.path === "string") { try { return { ...pathConfig, path: globToRegexp(pathConfig.path) }; } catch (e) { console.error(`Invalid glob pattern: ${pathConfig}`); return pathConfig; } } return pathConfig; }); return { ...config, privatePaths: convertedPaths }; } return config; }