diff --git a/src/pages/api/bookmarks.js b/src/pages/api/bookmarks.js index ae50c279..a7b0ae17 100644 --- a/src/pages/api/bookmarks.js +++ b/src/pages/api/bookmarks.js @@ -1,7 +1,8 @@ -import { createAuthFromSettings } from "utils/auth/auth-helpers"; +import { createAuthProvider } from "utils/auth/auth-helpers"; import { bookmarksResponse } from "utils/config/api-response"; +import { getSettings } from "utils/config/config"; export default async function handler(req, res) { - const auth = createAuthFromSettings() + const auth = createAuthProvider(getSettings()) res.send(await bookmarksResponse(auth.permissions(req))); } diff --git a/src/pages/api/services/index.js b/src/pages/api/services/index.js index 3140d6eb..749ee420 100644 --- a/src/pages/api/services/index.js +++ b/src/pages/api/services/index.js @@ -1,8 +1,14 @@ -import { createAuthFromSettings } from "utils/auth/auth-helpers"; +import { createAuthProvider } from "utils/auth/auth-helpers"; import { servicesResponse } from "utils/config/api-response"; +import { getSettings } from "utils/config/config"; +import createLogger from "utils/logger"; + +let logger = createLogger("services_index") export default async function handler(req, res) { - const auth = createAuthFromSettings() - - res.send(await servicesResponse(auth.permissions(req))); + logger.log("Call services"); + const auth = createAuthProvider(getSettings) + const result = await servicesResponse(auth.permissions(req)) + logger.log(result); + res.send(result); } diff --git a/src/pages/api/widgets/index.js b/src/pages/api/widgets/index.js index eae40397..beb642f3 100644 --- a/src/pages/api/widgets/index.js +++ b/src/pages/api/widgets/index.js @@ -1,8 +1,9 @@ -import { createAuthFromSettings } from "utils/auth/auth-helpers"; +import { createAuthProvider } from "utils/auth/auth-helpers"; import { widgetsResponse } from "utils/config/api-response"; +import { getSettings } from "utils/config/config"; export default async function handler(req, res) { - const auth = createAuthFromSettings(); + const auth = createAuthProvider(getSettings()); res.send(await widgetsResponse(auth.permissions(req))); } diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 590d39bb..6701b3a8 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -1,7 +1,6 @@ /* eslint-disable react/no-array-index-key */ -import useSWR, { SWRConfig } from "swr"; +import useSWR, { unstable_serialize, SWRConfig } from "swr"; import Head from "next/head"; -import {headers} from "next/header"; import dynamic from "next/dynamic"; import classNames from "classnames"; import { useTranslation } from "next-i18next"; @@ -28,8 +27,8 @@ import ErrorBoundary from "components/errorboundry"; import themes from "utils/styles/themes"; import QuickLaunch from "components/quicklaunch"; import { getStoredProvider, searchProviders } from "components/widgets/search/search"; -import { NullPermissions, createAuthFromSettings } from "utils/auth/auth-helpers"; - +import { createAuthorizer, fetchWithAuth } from "utils/auth/auth-helpers"; +import { NullAuthProvider } from "utils/auth/null"; const ThemeToggle = dynamic(() => import("components/toggles/theme"), { ssr: false, }); @@ -44,25 +43,28 @@ const Version = dynamic(() => import("components/version"), { const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "openmeteo", "search", "datetime"]; -export async function getStaticProps() { +export async function getServerSideProps({req}) { let logger; try { logger = createLogger("index"); const { providers, auth, ...settings } = getSettings(); + const authProvider = createAuthorizer({auth: auth}); - const services = await servicesResponse(NullPermissions); - const bookmarks = await bookmarksResponse(NullPermissions); - const widgets = await widgetsResponse(NullPermissions); + const services = await servicesResponse(authProvider.authorize(req)); + const bookmarks = await bookmarksResponse(authProvider.authorize(req)); + const widgets = await widgetsResponse(authProvider.authorize(req)); + const authContext = authProvider.getContext(req); return { props: { initialSettings: settings, fallback: { - "/api/services": services, - "/api/bookmarks": bookmarks, - "/api/widgets": widgets, + [unstable_serialize(["/api/services", authContext])]: services, + [unstable_serialize(["/api/bookmarks", authContext])]: bookmarks, + [unstable_serialize(["/api/widgets", authContext])]: widgets, "/api/hash": false, }, + authContext: authContext, ...(await serverSideTranslations(settings.language ?? "en")), }, }; @@ -70,22 +72,24 @@ export async function getStaticProps() { if (logger) { logger.error(e); } + const authContext = NullAuthProvider.create().getContext(req); return { props: { initialSettings: {}, fallback: { - "/api/services": [], - "/api/bookmarks": [], - "/api/widgets": [], + [unstable_serialize(["/api/services", authContext])]: [], + [unstable_serialize(["/api/bookmarks", authContext])]: [], + [unstable_serialize(["/api/widgets", authContext])]: [], "/api/hash": false, }, + authContext: authContext, ...(await serverSideTranslations("en")), }, }; } } -function Index({ initialSettings, fallback }) { +function Index({ initialSettings, fallback, authContext }) { const windowFocused = useWindowFocus(); const [stale, setStale] = useState(false); const { data: errorsData } = useSWR("/api/validate"); @@ -155,7 +159,7 @@ function Index({ initialSettings, fallback }) { return ( fetch(resource, init).then((res) => res.json()) }}> - + ); @@ -169,7 +173,7 @@ const headerStyles = { boxedWidgets: "m-6 mb-0 sm:m-9 sm:mb-0 sm:mt-1", }; -function Home({ initialSettings }) { +function Home({ initialSettings, authContext }) { const { i18n } = useTranslation(); const { theme, setTheme } = useContext(ThemeContext); const { color, setColor } = useContext(ColorContext); @@ -181,11 +185,9 @@ function Home({ initialSettings }) { setSettings(initialSettings); }, [initialSettings, setSettings]); - const auth = createAuthFromSettings(); - - const { data: services } = useSWR(auth.cacheContext("/api/services"), auth.fetcher); - const { data: bookmarks } = useSWR(auth.cacheContext("/api/bookmarks"), auth.fetcher); - const { data: widgets } = useSWR(auth.cacheContext("/api/widgets"), auth.fetcher); + const { data: services } = useSWR(["/api/services", authContext], fetchWithAuth); + const { data: bookmarks } = useSWR(["/api/bookmarks", authContext], fetchWithAuth); + const { data: widgets } = useSWR(["/api/widgets", authContext], fetchWithAuth); const servicesAndBookmarks = [ ...services.map((sg) => sg.services).flat(), @@ -470,7 +472,7 @@ function Home({ initialSettings }) { ); } -export default function Wrapper({ initialSettings, fallback }) { +export default function Wrapper({ initialSettings, fallback, authContext }) { const wrappedStyle = {}; let backgroundBlur = false; let backgroundSaturate = false; @@ -521,7 +523,7 @@ export default function Wrapper({ initialSettings, fallback }) { backgroundBrightness && `backdrop-brightness-${initialSettings.background.brightness}`, )} > - + diff --git a/src/utils/auth/auth-helpers.js b/src/utils/auth/auth-helpers.js index bf3ae242..a6c2ccbb 100644 --- a/src/utils/auth/auth-helpers.js +++ b/src/utils/auth/auth-helpers.js @@ -1,41 +1,42 @@ -import { getSettings } from "utils/config/config"; -import { ProxyAuthKey, createProxyAuth } from "./proxy"; +import { ProxyAuthProvider} from "./proxy"; +import { NullAuthProvider} from "./null"; -export const NullPermissions = { user: null, groups:[]} +const AuthProviders = { + NullAuthProvider, + ProxyAuthProvider +}; -export const NullAuth = { - permissions: (request) => NullPermissions, - cacheContext: (key) => key, - fetcher: (key) => fetch(key).then((res) => res.json()) +function getProviderByKey(key) { + return AuthProviders.find((provider) => provider.key == key) ?? NullAuthProvider; } -export function createAuthFromSettings() { - const {auth} = getSettings(); +export function createAuthorizer({auth}) { if (auth) { - switch (Object.keys(auth)[0]) { - case ProxyAuthKey: - return createProxyAuth(auth[ProxyAuthKey]); - default: - return NullAuth; - } + getProviderByKey(Object.keys(auth)[0]).create(auth[ProxyAuthKey]); } - return NullAuth + return NullAuthProvider.create(); +} + +export async function fetchWithAuth(key, context) { + return getProviderByKey(context.provider).fetch([key, context]); } export const filterAllowedServices = (perms, services) => filterAllowedItems(perms, services, 'services'); export const filterAllowedBookmarks = (perms, bookmarks) => filterAllowedItems(perms, bookmarks, 'bookmarks'); -export const filterAllowedWidgets = (perms, widgets) => filterAllowedItems(perms, widgets, 'widgets') +export const filterAllowedWidgets = (perms, widgets) => { + return widgets.filter((widget) => authItemFilter(perms, widget.options) ) +} function filterAllowedItems({user, groups}, itemGroups, groupKey) { return itemGroups.map((group) => ({ name: group.name, [groupKey]: group[groupKey].filter((item) => authItemFilter({user, groups}, item)) - })).filter((group) => !group[groupKey].length) + })).filter((group) => !group[groupKey].length); } function authItemFilter({user, groups}, item) { - const groupAllow = (!(allowGroups in item)) || groups.some(group => item.allowGroups.includes(group)); - const userAllow = (!(allowUsers in item)) || item.allowUsers.includes(user); + const groupAllow = (!('allowGroups' in item)) || groups.some(group => item.allowGroups.includes(group)); + const userAllow = (!('allowUsers' in item)) || item.allowUsers.includes(user); return userAllow || groupAllow; } diff --git a/src/utils/auth/null.js b/src/utils/auth/null.js new file mode 100644 index 00000000..f1656823 --- /dev/null +++ b/src/utils/auth/null.js @@ -0,0 +1,21 @@ +const NullPermissions = { user: null, groups:[]} +const NullAuthKey = "none" + +function createNullAuth() { + return { + authorize: (request) => NullPermissions, + getContext: (request) => { return { + provider: NullAuthKey + } }, + } +} + +async function fetchNullAuth([key, context]) { + return fetch(key).then((res) => res.json()) +} + +export const NullAuthProvider = { + key: NullAuthKey, + create: createNullAuth, + fetch: fetchNullAuth +} diff --git a/src/utils/auth/proxy.js b/src/utils/auth/proxy.js index 17c51328..7e824cb0 100644 --- a/src/utils/auth/proxy.js +++ b/src/utils/auth/proxy.js @@ -1,42 +1,35 @@ // Proxy auth is meant to be used by a reverse proxy that injects permission headers into the origin // request. In this case we are relying on our proxy to authenitcate our users and validate. -import {createLogger} from "utils/logger"; -import { headers } from 'next/headers'; - -export const ProxyAuthKey="proxy_auth" - +const ProxyAuthKey="proxy_auth" function getProxyPermissions(userHeader, groupHeader, request) { - const logger = createLogger("proxyAuth") const user = (userHeader)?request.headers.get(userHeader):None; - if (!user) { - logger.debug("unable to retreive user. User header doesn't exist or unspecified.") - } const groupsString = (groupHeader)?request.headers.get(groupHeader):""; - if (!groupsString) { - logger.debug("unable to retrieve groups. Groups header doesn't exist or unspecified") - } return {user: user, groups: (groupsString)?groupsString.split(",").map((v) => v.trimStart()):[]} } -export function createProxyAuth({groupHeader, userHeader}) { - const logger = createLogger("proxyAuth") - - if (!userHeader) { - logger.debug("'userHeader' value not specified"); - } - if (!groupHeader) { - logger.debug("'groupHeader' value not specified") - } +function createProxyAuth({groupHeader, userHeader}) { return { - permissions : (request) => getProxyPermissions(userHeader, groupHeader, request), - cacheContext: (key) => [ key, { - ...userHeader && {[userHeader]: headers.get(userHeader) }, - ...groupHeader && {[groupHeader]: headers.get(groupHeader)} - }], - fetcher: ([key, context]) => { - fetch(key, {headers: context}).then((res) => res.json()) - } + getContext : (request) => { + return { + provider: ProxyAuthKey, + headers: { + ...userHeader && {[userHeader]: request.headers.get(userHeader) }, + ...groupHeader && {[groupHeader]: request.headers.get(groupHeader)} + } + } + }, + authorize : (request) => getProxyPermissions(userHeader, groupHeader, request) } +} + +async function fetchProxyAuth([key, context]) { + return fetch(key, {headers: context.headers}).then((res) => res.json()) +} + +export const ProxyAuthProvider = { + key: ProxyAuthKey, + create: createProxyAuth, + fetch: fetchProxyAuth } \ No newline at end of file diff --git a/src/utils/config/api-response.js b/src/utils/config/api-response.js index bd86670e..62937e8a 100644 --- a/src/utils/config/api-response.js +++ b/src/utils/config/api-response.js @@ -83,7 +83,7 @@ export async function widgetsResponse(perms) { let configuredWidgets; try { - configuredWidgets = filterAllowedWidgets(perms, cleanWidgetGroups(await widgetsFromConfig())); + configuredWidgets = filterAllowedWidgets(perms, await cleanWidgetGroups(await widgetsFromConfig())); } catch (e) { console.error("Failed to load widgets, please check widgets.yaml for errors or remove example entries."); if (e) console.error(e);