Initial add auth to homepage
This commit is contained in:
parent
654f16dbb5
commit
2fee5a8db7
14
environment.yml
Normal file
14
environment.yml
Normal file
@ -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(),
|
||||
|
||||
42
src/utils/auth/auth-helpers.js
Normal file
42
src/utils/auth/auth-helpers.js
Normal file
@ -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;
|
||||
}
|
||||
|
||||
42
src/utils/auth/proxy.js
Normal file
42
src/utils/auth/proxy.js
Normal file
@ -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,7 +31,7 @@ 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");
|
||||
@ -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…
Reference in New Issue
Block a user