From 7ca152992c7bee956c9c1b9eafb7959a38688b6f Mon Sep 17 00:00:00 2001 From: Sara Date: Wed, 1 Nov 2023 13:48:32 +0100 Subject: [PATCH] fix typings and edge config key issue --- www/app/[domain]/domainContext.tsx | 33 ++++++++++++++---------------- www/app/[domain]/layout.tsx | 7 +++---- www/app/lib/edgeConfig.ts | 31 ++++++++++++++++++++++++++++ www/app/lib/fief.ts | 8 +++----- www/app/lib/getApi.ts | 2 +- www/middleware.ts | 12 +++++------ 6 files changed, 58 insertions(+), 35 deletions(-) create mode 100644 www/app/lib/edgeConfig.ts diff --git a/www/app/[domain]/domainContext.tsx b/www/app/[domain]/domainContext.tsx index 0b89c6e7..1228bf8d 100644 --- a/www/app/[domain]/domainContext.tsx +++ b/www/app/[domain]/domainContext.tsx @@ -1,14 +1,8 @@ "use client"; import { createContext, useContext, useEffect, useState } from "react"; +import { DomainConfig } from "../lib/edgeConfig"; -type DomainContextType = { - features: { - requireLogin: boolean; - privacy: boolean; - browse: boolean; - }; - apiUrl: string | null; -}; +type DomainContextType = Omit; export const DomainContext = createContext({ features: { @@ -16,22 +10,22 @@ export const DomainContext = createContext({ privacy: true, browse: false, }, - apiUrl: null, + api_url: "", }); -export const DomainContextProvider = ({ config, children }) => { +export const DomainContextProvider = ({ + config, + children, +}: { + config: DomainConfig; + children: any; +}) => { const [context, setContext] = useState(); useEffect(() => { if (!config) return; - setContext({ - features: { - requireLogin: !!config["features"]["requireLogin"], - privacy: !!config["features"]["privacy"], - browse: !!config["features"]["browse"], - }, - apiUrl: config["api_url"], - }); + const { auth_callback_url, ...others } = config; + setContext(others); }, [config]); if (!context) return; @@ -41,9 +35,12 @@ export const DomainContextProvider = ({ config, children }) => { ); }; +// Get feature config client-side with export const featureEnabled = ( featureName: "requireLogin" | "privacy" | "browse", ) => { const context = useContext(DomainContext); return context.features[featureName] as boolean | undefined; }; + +// Get config server-side (out of react) : see lib/edgeConfig. diff --git a/www/app/[domain]/layout.tsx b/www/app/[domain]/layout.tsx index ea2f9baa..49f584c6 100644 --- a/www/app/[domain]/layout.tsx +++ b/www/app/[domain]/layout.tsx @@ -11,6 +11,7 @@ import About from "../(aboutAndPrivacy)/about"; import Privacy from "../(aboutAndPrivacy)/privacy"; import { get } from "@vercel/edge-config"; import { DomainContextProvider } from "./domainContext"; +import { getConfig } from "../lib/edgeConfig"; const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] }); @@ -68,10 +69,8 @@ type LayoutProps = { }; export default async function RootLayout({ children, params }: LayoutProps) { - const config = await get(params.domain); - const requireLogin = config ? config["features"]["requireLogin"] : false; - const privacy = config ? config["features"]["privacy"] : true; - const browse = config ? config["features"]["browse"] : true; + const config = await getConfig(params.domain); + const { requireLogin, privacy, browse } = config.features; return ( diff --git a/www/app/lib/edgeConfig.ts b/www/app/lib/edgeConfig.ts new file mode 100644 index 00000000..deb79e09 --- /dev/null +++ b/www/app/lib/edgeConfig.ts @@ -0,0 +1,31 @@ +import { get } from "@vercel/edge-config"; + +type EdgeConfig = { + [domainWithDash: string]: { + features: { + [featureName in "requireLogin" | "privacy" | "browse"]: boolean; + }; + auth_callback_url: string; + api_url: string; + }; +}; + +export type DomainConfig = EdgeConfig["domainWithDash"]; + +// Edge config main keys can only be alphanumeric and _ or - +export function edgeKeyToDomain(key: string) { + return key.replaceAll(".", "_"); +} + +export function edgeDomainToKey(domain: string) { + return domain.replaceAll("_", "."); +} + +// get edge config server-side (prefer DomainContext when available), domain is the hostname +export async function getConfig(domain: string) { + const config = await get(edgeDomainToKey(domain)); + + if (typeof config !== "object") throw Error("Error fetchig config"); + + return config as DomainConfig; +} diff --git a/www/app/lib/fief.ts b/www/app/lib/fief.ts index 2ea118b6..4e1dec2d 100644 --- a/www/app/lib/fief.ts +++ b/www/app/lib/fief.ts @@ -1,8 +1,6 @@ import { Fief, FiefUserInfo } from "@fief/fief"; import { FiefAuth, IUserInfoCache } from "@fief/fief/nextjs"; -import { get } from "@vercel/edge-config"; -import { NextRequest, NextResponse } from "next/server"; -import { useError } from "../(errors)/errorContext"; +import { getConfig } from "./edgeConfig"; export const SESSION_COOKIE_NAME = "reflector-auth"; @@ -46,12 +44,12 @@ export const getFiefAuth = async (url: URL) => { if (FIEF_AUTHS[url.hostname]) { return FIEF_AUTHS[url.hostname]; } else { - const config = url && (await get(url.hostname)); + const config = url && (await getConfig(url.hostname)); if (config) { FIEF_AUTHS[url.hostname] = new FiefAuth({ client: fiefClient, sessionCookieName: SESSION_COOKIE_NAME, - redirectURI: config["auth_callback_url"], + redirectURI: config.auth_callback_url, logoutRedirectURI: url.origin, userInfoCache: new MemoryUserInfoCache(), }); diff --git a/www/app/lib/getApi.ts b/www/app/lib/getApi.ts index 8d52ec20..9347d9b8 100644 --- a/www/app/lib/getApi.ts +++ b/www/app/lib/getApi.ts @@ -7,7 +7,7 @@ import { DomainContext } from "../[domain]/domainContext"; export default function getApi(): DefaultApi { const accessTokenInfo = useFiefAccessTokenInfo(); - const api_url = useContext(DomainContext).apiUrl; + const api_url = useContext(DomainContext).api_url; if (!api_url) throw new Error("no API URL"); const apiConfiguration = new Configuration({ diff --git a/www/middleware.ts b/www/middleware.ts index 7b593ac4..024e3697 100644 --- a/www/middleware.ts +++ b/www/middleware.ts @@ -1,24 +1,22 @@ import { NextResponse, NextRequest } from "next/server"; import { get } from "@vercel/edge-config"; -import { FiefAuth, IUserInfoCache } from "@fief/fief/nextjs"; -import { getFiefAuth, getFiefAuthMiddleware } from "./app/lib/fief"; +import { getFiefAuthMiddleware } from "./app/lib/fief"; +import { getConfig } from "./app/lib/edgeConfig"; export async function middleware(request: NextRequest) { const domain = request.nextUrl.hostname; - const config = await get(domain); - - if (!config) return NextResponse.error(); + const config = await getConfig(domain); // Feature-flag protedted paths if ( - !config["features"]["browse"] && + !config.features.browse && request.nextUrl.pathname.startsWith("/browse") ) { return NextResponse.redirect(request.nextUrl.origin); } - if (config["features"]["requireLogin"]) { + if (config.features.requireLogin) { const fiefMiddleware = await getFiefAuthMiddleware(request.nextUrl); const fiefResponse = fiefMiddleware(request); if (