Initial add auth to homepage

pull/2855/head
Aaron Dalton 7 months ago committed by Aaron Dalton
parent 8eb61ef9ff
commit 9aec3b05e6

@ -0,0 +1,14 @@
name: node18
channels:
- conda-forge
- defaults
dependencies:
- ca-certificates=2023.7.22=hf0a4a13_0
- icu=73.2=hc8870d7_0
- libcxx=16.0.6=h4653b0c_0
- libuv=1.46.0=hb547adb_0
- libzlib=1.2.13=h53f4e23_5
- nodejs=18.17.1=h7ed3092_1
- openssl=3.1.4=h0d3ecfb_0
- pnpm=8.10.0=h992f1b1_0
- zlib=1.2.13=h53f4e23_5

@ -1,5 +1,7 @@
import { createAuthFromSettings } from "utils/auth/auth-helpers";
import { bookmarksResponse } from "utils/config/api-response";
export default async function handler(req, res) {
res.send(await bookmarksResponse());
const auth = createAuthFromSettings()
res.send(await bookmarksResponse(auth.permissions(req)));
}

@ -1,5 +1,8 @@
import { createAuthFromSettings } from "utils/auth/auth-helpers";
import { servicesResponse } from "utils/config/api-response";
export default async function handler(req, res) {
res.send(await servicesResponse());
const auth = createAuthFromSettings()
res.send(await servicesResponse(auth.permissions(req)));
}

@ -1,5 +1,8 @@
import { createAuthFromSettings } from "utils/auth/auth-helpers";
import { widgetsResponse } from "utils/config/api-response";
export default async function handler(req, res) {
res.send(await widgetsResponse());
const auth = createAuthFromSettings();
res.send(await widgetsResponse(auth.permissions(req)));
}

@ -1,6 +1,7 @@
/* eslint-disable react/no-array-index-key */
import useSWR, { 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";
@ -27,6 +28,7 @@ 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";
const ThemeToggle = dynamic(() => import("components/toggles/theme"), {
ssr: false,
@ -46,11 +48,11 @@ export async function getStaticProps() {
let logger;
try {
logger = createLogger("index");
const { providers, ...settings } = getSettings();
const { providers, auth, ...settings } = getSettings();
const services = await servicesResponse();
const bookmarks = await bookmarksResponse();
const widgets = await widgetsResponse();
const services = await servicesResponse(NullPermissions);
const bookmarks = await bookmarksResponse(NullPermissions);
const widgets = await widgetsResponse(NullPermissions);
return {
props: {
@ -179,9 +181,11 @@ function Home({ initialSettings }) {
setSettings(initialSettings);
}, [initialSettings, setSettings]);
const { data: services } = useSWR("/api/services");
const { data: bookmarks } = useSWR("/api/bookmarks");
const { data: widgets } = useSWR("/api/widgets");
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 servicesAndBookmarks = [
...services.map((sg) => sg.services).flat(),

@ -0,0 +1,42 @@
import { getSettings } from "utils/config/config";
import { ProxyAuthKey, createProxyAuth } from "./proxy";
export const NullPermissions = { user: null, groups:[]}
export const NullAuth = {
permissions: (request) => NullPermissions,
cacheContext: (key) => key,
fetcher: (key) => fetch(key).then((res) => res.json())
}
export function createAuthFromSettings() {
const {auth} = getSettings();
if (auth) {
switch (Object.keys(auth)[0]) {
case ProxyAuthKey:
return createProxyAuth(auth[ProxyAuthKey]);
default:
return NullAuth;
}
}
return NullAuth
}
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')
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)
}
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);
return userAllow || groupAllow;
}

@ -0,0 +1,42 @@
// 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"
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")
}
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())
}
}
}

@ -12,6 +12,13 @@ import {
servicesFromKubernetes,
} from "utils/config/service-helpers";
import { cleanWidgetGroups, widgetsFromConfig } from "utils/config/widget-helpers";
import { filterAuthBookmarks } from "utils/auth/auth-helpers";
import {
filterAllowedBookmarks,
filterAllowedServices,
filterAllowedWidgets
} from "utils/auth/auth-helpers";
/**
* Compares services by weight then by name.
@ -24,13 +31,13 @@ function compareServices(service1, service2) {
return service1.name.localeCompare(service2.name);
}
export async function bookmarksResponse() {
export async function bookmarksResponse(perms) {
checkAndCopyConfig("bookmarks.yaml");
const bookmarksYaml = path.join(CONF_DIR, "bookmarks.yaml");
const rawFileContents = await fs.readFile(bookmarksYaml, "utf8");
const fileContents = substituteEnvironmentVars(rawFileContents);
const bookmarks = yaml.load(fileContents);
const bookmarks = yaml.load(fileContents);
if (!bookmarks) return [];
@ -45,13 +52,15 @@ export async function bookmarksResponse() {
}
// map easy to write YAML objects into easy to consume JS arrays
const bookmarksArray = bookmarks.map((group) => ({
name: Object.keys(group)[0],
bookmarks: group[Object.keys(group)[0]].map((entries) => ({
name: Object.keys(entries)[0],
...entries[Object.keys(entries)[0]][0],
})),
}));
const bookmarksArray = filterAllowedBookmarks(perms,
bookmarks.map((group) => ({
name: Object.keys(group)[0],
bookmarks: group[Object.keys(group)[0]].map((entries) => ({
name: Object.keys(entries)[0],
...entries[Object.keys(entries)[0]][0],
})),
}))
);
const sortedGroups = [];
const unsortedGroups = [];
@ -70,11 +79,11 @@ export async function bookmarksResponse() {
return [...sortedGroups.filter((g) => g), ...unsortedGroups];
}
export async function widgetsResponse() {
export async function widgetsResponse(perms) {
let configuredWidgets;
try {
configuredWidgets = cleanWidgetGroups(await widgetsFromConfig());
configuredWidgets = filterAllowedWidgets(perms, 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);
@ -84,14 +93,14 @@ export async function widgetsResponse() {
return configuredWidgets;
}
export async function servicesResponse() {
export async function servicesResponse(perms) {
let discoveredDockerServices;
let discoveredKubernetesServices;
let configuredServices;
let initialSettings;
try {
discoveredDockerServices = cleanServiceGroups(await servicesFromDocker());
discoveredDockerServices = filterAllowedServices(perms, cleanServiceGroups(await servicesFromDocker()));
if (discoveredDockerServices?.length === 0) {
console.debug("No containers were found with homepage labels.");
}
@ -102,7 +111,7 @@ export async function servicesResponse() {
}
try {
discoveredKubernetesServices = cleanServiceGroups(await servicesFromKubernetes());
discoveredKubernetesServices = filterAllowedServices(perms, cleanServiceGroups(await servicesFromKubernetes()));
} catch (e) {
console.error("Failed to discover services, please check kubernetes.yaml for errors or remove example entries.");
if (e) console.error(e.toString());
@ -110,7 +119,7 @@ export async function servicesResponse() {
}
try {
configuredServices = cleanServiceGroups(await servicesFromConfig());
configuredServices = filterAllowedServices(perms, cleanServiceGroups(await servicesFromConfig()));
} catch (e) {
console.error("Failed to load services.yaml, please check for errors");
if (e) console.error(e.toString());

Loading…
Cancel
Save