First somewhat working image

This commit is contained in:
Aaron Dalton 2023-11-07 07:41:11 -05:00
parent 2fee5a8db7
commit a8bedc24a4
8 changed files with 107 additions and 82 deletions

View File

@ -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)));
}

View File

@ -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);
}

View File

@ -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)));
}

View File

@ -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 (
<SWRConfig value={{ fallback, fetcher: (resource, init) => fetch(resource, init).then((res) => res.json()) }}>
<ErrorBoundary>
<Home initialSettings={initialSettings} />
<Home initialSettings={initialSettings} authContext={authContext}/>
</ErrorBoundary>
</SWRConfig>
);
@ -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}`,
)}
>
<Index initialSettings={initialSettings} fallback={fallback} />
<Index initialSettings={initialSettings} fallback={fallback} authContext={authContext}/>
</div>
</div>
</div>

View File

@ -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;
}

21
src/utils/auth/null.js Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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);