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";
|
import { bookmarksResponse } from "utils/config/api-response";
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
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";
|
import { servicesResponse } from "utils/config/api-response";
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
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";
|
import { widgetsResponse } from "utils/config/api-response";
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
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 */
|
/* eslint-disable react/no-array-index-key */
|
||||||
import useSWR, { SWRConfig } from "swr";
|
import useSWR, { SWRConfig } from "swr";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
import {headers} from "next/header";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
@ -27,6 +28,7 @@ import ErrorBoundary from "components/errorboundry";
|
|||||||
import themes from "utils/styles/themes";
|
import themes from "utils/styles/themes";
|
||||||
import QuickLaunch from "components/quicklaunch";
|
import QuickLaunch from "components/quicklaunch";
|
||||||
import { getStoredProvider, searchProviders } from "components/widgets/search/search";
|
import { getStoredProvider, searchProviders } from "components/widgets/search/search";
|
||||||
|
import { NullPermissions, createAuthFromSettings } from "utils/auth/auth-helpers";
|
||||||
|
|
||||||
const ThemeToggle = dynamic(() => import("components/toggles/theme"), {
|
const ThemeToggle = dynamic(() => import("components/toggles/theme"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@ -46,11 +48,11 @@ export async function getStaticProps() {
|
|||||||
let logger;
|
let logger;
|
||||||
try {
|
try {
|
||||||
logger = createLogger("index");
|
logger = createLogger("index");
|
||||||
const { providers, ...settings } = getSettings();
|
const { providers, auth, ...settings } = getSettings();
|
||||||
|
|
||||||
const services = await servicesResponse();
|
const services = await servicesResponse(NullPermissions);
|
||||||
const bookmarks = await bookmarksResponse();
|
const bookmarks = await bookmarksResponse(NullPermissions);
|
||||||
const widgets = await widgetsResponse();
|
const widgets = await widgetsResponse(NullPermissions);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
@ -179,9 +181,11 @@ function Home({ initialSettings }) {
|
|||||||
setSettings(initialSettings);
|
setSettings(initialSettings);
|
||||||
}, [initialSettings, setSettings]);
|
}, [initialSettings, setSettings]);
|
||||||
|
|
||||||
const { data: services } = useSWR("/api/services");
|
const auth = createAuthFromSettings();
|
||||||
const { data: bookmarks } = useSWR("/api/bookmarks");
|
|
||||||
const { data: widgets } = useSWR("/api/widgets");
|
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 = [
|
const servicesAndBookmarks = [
|
||||||
...services.map((sg) => sg.services).flat(),
|
...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,
|
servicesFromKubernetes,
|
||||||
} from "utils/config/service-helpers";
|
} from "utils/config/service-helpers";
|
||||||
import { cleanWidgetGroups, widgetsFromConfig } from "utils/config/widget-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.
|
* Compares services by weight then by name.
|
||||||
@ -24,7 +31,7 @@ function compareServices(service1, service2) {
|
|||||||
return service1.name.localeCompare(service2.name);
|
return service1.name.localeCompare(service2.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bookmarksResponse() {
|
export async function bookmarksResponse(perms) {
|
||||||
checkAndCopyConfig("bookmarks.yaml");
|
checkAndCopyConfig("bookmarks.yaml");
|
||||||
|
|
||||||
const bookmarksYaml = path.join(CONF_DIR, "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
|
// map easy to write YAML objects into easy to consume JS arrays
|
||||||
const bookmarksArray = bookmarks.map((group) => ({
|
const bookmarksArray = filterAllowedBookmarks(perms,
|
||||||
|
bookmarks.map((group) => ({
|
||||||
name: Object.keys(group)[0],
|
name: Object.keys(group)[0],
|
||||||
bookmarks: group[Object.keys(group)[0]].map((entries) => ({
|
bookmarks: group[Object.keys(group)[0]].map((entries) => ({
|
||||||
name: Object.keys(entries)[0],
|
name: Object.keys(entries)[0],
|
||||||
...entries[Object.keys(entries)[0]][0],
|
...entries[Object.keys(entries)[0]][0],
|
||||||
})),
|
})),
|
||||||
}));
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const sortedGroups = [];
|
const sortedGroups = [];
|
||||||
const unsortedGroups = [];
|
const unsortedGroups = [];
|
||||||
@ -70,11 +79,11 @@ export async function bookmarksResponse() {
|
|||||||
return [...sortedGroups.filter((g) => g), ...unsortedGroups];
|
return [...sortedGroups.filter((g) => g), ...unsortedGroups];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function widgetsResponse() {
|
export async function widgetsResponse(perms) {
|
||||||
let configuredWidgets;
|
let configuredWidgets;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
configuredWidgets = cleanWidgetGroups(await widgetsFromConfig());
|
configuredWidgets = filterAllowedWidgets(perms, cleanWidgetGroups(await widgetsFromConfig()));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to load widgets, please check widgets.yaml for errors or remove example entries.");
|
console.error("Failed to load widgets, please check widgets.yaml for errors or remove example entries.");
|
||||||
if (e) console.error(e);
|
if (e) console.error(e);
|
||||||
@ -84,14 +93,14 @@ export async function widgetsResponse() {
|
|||||||
return configuredWidgets;
|
return configuredWidgets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function servicesResponse() {
|
export async function servicesResponse(perms) {
|
||||||
let discoveredDockerServices;
|
let discoveredDockerServices;
|
||||||
let discoveredKubernetesServices;
|
let discoveredKubernetesServices;
|
||||||
let configuredServices;
|
let configuredServices;
|
||||||
let initialSettings;
|
let initialSettings;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
discoveredDockerServices = cleanServiceGroups(await servicesFromDocker());
|
discoveredDockerServices = filterAllowedServices(perms, cleanServiceGroups(await servicesFromDocker()));
|
||||||
if (discoveredDockerServices?.length === 0) {
|
if (discoveredDockerServices?.length === 0) {
|
||||||
console.debug("No containers were found with homepage labels.");
|
console.debug("No containers were found with homepage labels.");
|
||||||
}
|
}
|
||||||
@ -102,7 +111,7 @@ export async function servicesResponse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
discoveredKubernetesServices = cleanServiceGroups(await servicesFromKubernetes());
|
discoveredKubernetesServices = filterAllowedServices(perms, cleanServiceGroups(await servicesFromKubernetes()));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to discover services, please check kubernetes.yaml for errors or remove example entries.");
|
console.error("Failed to discover services, please check kubernetes.yaml for errors or remove example entries.");
|
||||||
if (e) console.error(e.toString());
|
if (e) console.error(e.toString());
|
||||||
@ -110,7 +119,7 @@ export async function servicesResponse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
configuredServices = cleanServiceGroups(await servicesFromConfig());
|
configuredServices = filterAllowedServices(perms, cleanServiceGroups(await servicesFromConfig()));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to load services.yaml, please check for errors");
|
console.error("Failed to load services.yaml, please check for errors");
|
||||||
if (e) console.error(e.toString());
|
if (e) console.error(e.toString());
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user