mirror of
https://github.com/DI0IK/homepage-plus.git
synced 2025-07-06 20:28:48 +00:00
homepage-plus
This commit is contained in:
parent
b5f4daa8ef
commit
dc45fa63b7
16 changed files with 270 additions and 60 deletions
4
.github/workflows/crowdin.yml
vendored
4
.github/workflows/crowdin.yml
vendored
|
@ -2,8 +2,8 @@ name: Crowdin Action
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '2 */12 * * *'
|
||||
# schedule:
|
||||
# - cron: '2 */12 * * *'
|
||||
push:
|
||||
paths: [
|
||||
'/public/locales/en/**',
|
||||
|
|
30
.github/workflows/docker-publish.yml
vendored
30
.github/workflows/docker-publish.yml
vendored
|
@ -6,15 +6,16 @@ name: Docker
|
|||
# documentation.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '20 0 * * *'
|
||||
# schedule:
|
||||
# - cron: '20 0 * * *'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- feature/**
|
||||
- dev
|
||||
- homepage-plus
|
||||
# Publish semver tags as releases.
|
||||
tags: [ 'v*.*.*' ]
|
||||
tags: [ 'v*.*.*-plus' ]
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- 'mkdocs.yml'
|
||||
|
@ -24,6 +25,7 @@ on:
|
|||
- 'docs/**'
|
||||
- 'mkdocs.yml'
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# github.repository as <account>/<repo>
|
||||
|
@ -45,20 +47,16 @@ jobs:
|
|||
python-version: 3.x
|
||||
-
|
||||
name: Check files
|
||||
uses: pre-commit/action@v3.0.1
|
||||
uses: pre-commit/action@v3.0.0
|
||||
|
||||
build:
|
||||
name: Docker Build & Push
|
||||
if: github.repository == 'gethomepage/homepage'
|
||||
runs-on: self-hosted
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- pre-commit
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
# This is used to complete the identity challenge
|
||||
# with sigstore/fulcio when running outside of PRs.
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
@ -75,13 +73,13 @@ jobs:
|
|||
|
||||
# This step is being disabled because the runner is on a self-hosted machine
|
||||
# where the cache will stick between runs.
|
||||
# - name: Cache Docker layers
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: /tmp/.buildx-cache
|
||||
# key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-buildx-
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
|
|
|
@ -36,9 +36,9 @@ RUN npm run telemetry \
|
|||
FROM docker.io/node:18-alpine AS runner
|
||||
LABEL org.opencontainers.image.title "Homepage"
|
||||
LABEL org.opencontainers.image.description "A self-hosted services landing page, with docker and service integrations."
|
||||
LABEL org.opencontainers.image.url="https://github.com/gethomepage/homepage"
|
||||
LABEL org.opencontainers.image.url="https://github.com/di0ik/homepage-plus"
|
||||
LABEL org.opencontainers.image.documentation='https://github.com/gethomepage/homepage/wiki'
|
||||
LABEL org.opencontainers.image.source='https://github.com/gethomepage/homepage'
|
||||
LABEL org.opencontainers.image.source='https://github.com/di0ik/homepage-plus'
|
||||
LABEL org.opencontainers.image.licenses='Apache-2.0'
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
|
|
@ -536,3 +536,42 @@ or per service widget (`services.yaml`) with:
|
|||
```
|
||||
|
||||
If either value is set to true, the error message will be hidden.
|
||||
|
||||
## Identity Based Visibiltiy
|
||||
|
||||
Basic user identity integration is implemeted via an `identity` section. An identity provider can be configured using the `provider` section with the given type. Currently the only provider supported is `proxy`, where the users identification and group membership are passed via HTTP Request headers (in plaintext). The expectation is that the application will be accessed only via an authenticating proxy (i.e traefik or nginx).
|
||||
|
||||
The group and user headers are both configurable like so:
|
||||
|
||||
```yaml
|
||||
identity:
|
||||
provider:
|
||||
type: proxy
|
||||
groupHeader: "X-group-header"
|
||||
userHeader: "X-user-header"
|
||||
```
|
||||
|
||||
Identity based visibility can be configured on the service, bookmark, and widget level using the `allowUsers` and `allowGroups` list. The default is to allow all users and groups.
|
||||
|
||||
```yaml
|
||||
- Example Servie:
|
||||
allowGroups:
|
||||
- Group1
|
||||
- Group2
|
||||
- Group3
|
||||
allowUsers:
|
||||
- User1
|
||||
- User2
|
||||
- User3
|
||||
```
|
||||
|
||||
Identity visibility for groups can be set in the `groups` under `identity`. In general the `groups` tag follows the format of the `layout` section. For example:
|
||||
|
||||
```yaml
|
||||
identity:
|
||||
groups:
|
||||
- My Service Group:
|
||||
allowGroups: ["Group1", "Group2"]
|
||||
- My Other Group:
|
||||
allowGroups: ["Group1"]
|
||||
```
|
||||
|
|
|
@ -4,9 +4,9 @@ site_name: Homepage
|
|||
site_url: https://gethomepage.dev/
|
||||
|
||||
# Repository
|
||||
repo_name: gethomepage/homepage
|
||||
repo_url: https://github.com/gethomepage/homepage
|
||||
edit_uri: https://github.com/gethomepage/homepage/tree/main/docs/
|
||||
repo_name: di0ik/homepage-plus
|
||||
repo_url: https://github.com/di0ik/homepage-plus
|
||||
edit_uri: https://github.com/di0ik/homepage-plus/tree/main/docs/
|
||||
|
||||
nav:
|
||||
- "Home":
|
||||
|
|
|
@ -22,7 +22,7 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
|
|||
const [searchSuggestions, setSearchSuggestions] = useState([]);
|
||||
|
||||
const { data: widgets } = useSWR("/api/widgets");
|
||||
const searchWidget = Object.values(widgets).find((w) => w.type === "search");
|
||||
const searchWidget = widgets && Object.values(widgets).find((w) => w.type === "search");
|
||||
|
||||
let searchProvider;
|
||||
|
||||
|
|
17
src/pages/api/auth.js
Normal file
17
src/pages/api/auth.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { checkAllowedGroup, readIdentitySettings } from "utils/identity/identity-helpers";
|
||||
import { getSettings } from "utils/config/config";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { group } = req.query;
|
||||
const { provider, groups } = readIdentitySettings(getSettings().identity);
|
||||
|
||||
try {
|
||||
if (checkAllowedGroup(provider.getIdentity(req), groups, group)) {
|
||||
res.json({ group });
|
||||
} else {
|
||||
res.status(401).json({ message: "Group unathorized" });
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).send("Error getting user identity");
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
import { readIdentitySettings } from "utils/identity/identity-helpers";
|
||||
import { bookmarksResponse } from "utils/config/api-response";
|
||||
import { getSettings } from "utils/config/config";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
res.send(await bookmarksResponse());
|
||||
const { provider, groups } = readIdentitySettings(getSettings().identity);
|
||||
res.send(await bookmarksResponse(provider.getIdentity(req), groups));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { readIdentitySettings } from "utils/identity/identity-helpers";
|
||||
import { servicesResponse } from "utils/config/api-response";
|
||||
import { getSettings } from "utils/config/config";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
res.send(await servicesResponse());
|
||||
const { provider, groups } = readIdentitySettings(getSettings().identity);
|
||||
res.send(await servicesResponse(provider.getIdentity(req), groups));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { readIdentitySettings } from "utils/identity/identity-helpers";
|
||||
import { widgetsResponse } from "utils/config/api-response";
|
||||
import { getSettings } from "utils/config/config";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
res.send(await widgetsResponse());
|
||||
const { provider } = readIdentitySettings(getSettings().identity);
|
||||
res.send(await widgetsResponse(provider.getIdentity(req)));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable react/no-array-index-key */
|
||||
import useSWR, { SWRConfig } from "swr";
|
||||
import useSWR, { unstable_serialize as unstableSerialize, SWRConfig } from "swr";
|
||||
import Head from "next/head";
|
||||
import Script from "next/script";
|
||||
import dynamic from "next/dynamic";
|
||||
|
@ -10,6 +10,7 @@ import { BiError } from "react-icons/bi";
|
|||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import NullIdentityProvider from "utils/identity/null";
|
||||
import Tab, { slugifyAndEncode } from "components/tab";
|
||||
import ServicesGroup from "components/services/group";
|
||||
import BookmarksGroup from "components/bookmarks/group";
|
||||
|
@ -26,6 +27,7 @@ import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/conf
|
|||
import ErrorBoundary from "components/errorboundry";
|
||||
import themes from "utils/styles/themes";
|
||||
import QuickLaunch from "components/quicklaunch";
|
||||
import { fetchWithIdentity, readIdentitySettings } from "utils/identity/identity-helpers";
|
||||
|
||||
const ThemeToggle = dynamic(() => import("components/toggles/theme"), {
|
||||
ssr: false,
|
||||
|
@ -41,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, ...settings } = getSettings();
|
||||
const { providers, identity, ...settings } = getSettings();
|
||||
const { provider, groups } = readIdentitySettings(identity);
|
||||
|
||||
const services = await servicesResponse();
|
||||
const bookmarks = await bookmarksResponse();
|
||||
const widgets = await widgetsResponse();
|
||||
const services = await servicesResponse(provider.getIdentity(req), groups);
|
||||
const bookmarks = await bookmarksResponse(provider.getIdentity(req), groups);
|
||||
const widgets = await widgetsResponse(provider.getIdentity(req));
|
||||
const identityContext = provider.getContext(req);
|
||||
|
||||
return {
|
||||
props: {
|
||||
initialSettings: settings,
|
||||
fallback: {
|
||||
"/api/services": services,
|
||||
"/api/bookmarks": bookmarks,
|
||||
"/api/widgets": widgets,
|
||||
[unstableSerialize(["/api/services", identityContext])]: services,
|
||||
[unstableSerialize(["/api/bookmarks", identityContext])]: bookmarks,
|
||||
[unstableSerialize(["/api/widgets", identityContext])]: widgets,
|
||||
"/api/hash": false,
|
||||
},
|
||||
identityContext,
|
||||
...(await serverSideTranslations(settings.language ?? "en")),
|
||||
},
|
||||
};
|
||||
|
@ -67,22 +72,24 @@ export async function getStaticProps() {
|
|||
if (logger && e) {
|
||||
logger.error(e);
|
||||
}
|
||||
const identityContext = NullIdentityProvider.create().getContext(req);
|
||||
return {
|
||||
props: {
|
||||
initialSettings: {},
|
||||
fallback: {
|
||||
"/api/services": [],
|
||||
"/api/bookmarks": [],
|
||||
"/api/widgets": [],
|
||||
[unstableSerialize(["/api/services", identityContext])]: [],
|
||||
[unstableSerialize(["/api/bookmarks", identityContext])]: [],
|
||||
[unstableSerialize(["/api/widgets", identityContext])]: [],
|
||||
"/api/hash": false,
|
||||
},
|
||||
identityContext,
|
||||
...(await serverSideTranslations("en")),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function Index({ initialSettings, fallback }) {
|
||||
function Index({ initialSettings, fallback, identityContext }) {
|
||||
const windowFocused = useWindowFocus();
|
||||
const [stale, setStale] = useState(false);
|
||||
const { data: errorsData } = useSWR("/api/validate");
|
||||
|
@ -152,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} identityContext={identityContext} />
|
||||
</ErrorBoundary>
|
||||
</SWRConfig>
|
||||
);
|
||||
|
@ -178,7 +185,7 @@ function getAllServices(services) {
|
|||
return [...services.map(getServices).flat()];
|
||||
}
|
||||
|
||||
function Home({ initialSettings }) {
|
||||
function Home({ initialSettings, identityContext }) {
|
||||
const { i18n } = useTranslation();
|
||||
const { theme, setTheme } = useContext(ThemeContext);
|
||||
const { color, setColor } = useContext(ColorContext);
|
||||
|
@ -190,9 +197,9 @@ 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 { data: services } = useSWR(["/api/services", identityContext], fetchWithIdentity);
|
||||
const { data: bookmarks } = useSWR(["/api/bookmarks", identityContext], fetchWithIdentity);
|
||||
const { data: widgets } = useSWR(["/api/widgets", identityContext], fetchWithIdentity);
|
||||
|
||||
const servicesAndBookmarks = [...bookmarks.map((bg) => bg.bookmarks).flat(), ...getAllServices(services)].filter(
|
||||
(i) => i?.href,
|
||||
|
@ -469,7 +476,7 @@ function Home({ initialSettings }) {
|
|||
);
|
||||
}
|
||||
|
||||
export default function Wrapper({ initialSettings, fallback }) {
|
||||
export default function Wrapper({ initialSettings, fallback, identityContext }) {
|
||||
const { themeContext } = useContext(ThemeContext);
|
||||
const wrappedStyle = {};
|
||||
let backgroundBlur = false;
|
||||
|
@ -522,7 +529,7 @@ export default function Wrapper({ initialSettings, fallback }) {
|
|||
backgroundBrightness && `backdrop-brightness-${initialSettings.background.brightness}`,
|
||||
)}
|
||||
>
|
||||
<Index initialSettings={initialSettings} fallback={fallback} />
|
||||
<Index initialSettings={initialSettings} fallback={fallback} identityContext={identityContext} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
findGroupByName,
|
||||
} from "utils/config/service-helpers";
|
||||
import { cleanWidgetGroups, widgetsFromConfig } from "utils/config/widget-helpers";
|
||||
import { filterAllowedBookmarks, filterAllowedServices, filterAllowedWidgets } from "utils/identity/identity-helpers";
|
||||
|
||||
/**
|
||||
* Compares services by weight then by name.
|
||||
|
@ -25,7 +26,7 @@ function compareServices(service1, service2) {
|
|||
return service1.name.localeCompare(service2.name);
|
||||
}
|
||||
|
||||
export async function bookmarksResponse() {
|
||||
export async function bookmarksResponse(perms, idGroups) {
|
||||
checkAndCopyConfig("bookmarks.yaml");
|
||||
|
||||
const bookmarksYaml = path.join(CONF_DIR, "bookmarks.yaml");
|
||||
|
@ -46,13 +47,17 @@ export async function bookmarksResponse() {
|
|||
}
|
||||
|
||||
// map easy to write YAML objects into easy to consume JS arrays
|
||||
const bookmarksArray = bookmarks.map((group) => ({
|
||||
const bookmarksArray = filterAllowedBookmarks(
|
||||
perms,
|
||||
idGroups,
|
||||
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 = [];
|
||||
|
@ -71,11 +76,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, 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);
|
||||
|
@ -96,14 +101,14 @@ function mergeSubgroups(configuredGroups, mergedGroup) {
|
|||
});
|
||||
}
|
||||
|
||||
export async function servicesResponse() {
|
||||
export async function servicesResponse(perms, idGroups) {
|
||||
let discoveredDockerServices;
|
||||
let discoveredKubernetesServices;
|
||||
let configuredServices;
|
||||
let initialSettings;
|
||||
|
||||
try {
|
||||
discoveredDockerServices = cleanServiceGroups(await servicesFromDocker());
|
||||
discoveredDockerServices = filterAllowedServices(perms, idGroups, cleanServiceGroups(await servicesFromDocker()));
|
||||
if (discoveredDockerServices?.length === 0) {
|
||||
console.debug("No containers were found with homepage labels.");
|
||||
}
|
||||
|
@ -114,7 +119,11 @@ export async function servicesResponse() {
|
|||
}
|
||||
|
||||
try {
|
||||
discoveredKubernetesServices = cleanServiceGroups(await servicesFromKubernetes());
|
||||
discoveredKubernetesServices = filterAllowedServices(
|
||||
perms,
|
||||
idGroups,
|
||||
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());
|
||||
|
@ -122,7 +131,7 @@ export async function servicesResponse() {
|
|||
}
|
||||
|
||||
try {
|
||||
configuredServices = cleanServiceGroups(await servicesFromConfig());
|
||||
configuredServices = filterAllowedServices(perms, idGroups, cleanServiceGroups(await servicesFromConfig()));
|
||||
} catch (e) {
|
||||
console.error("Failed to load services.yaml, please check for errors");
|
||||
if (e) console.error(e.toString());
|
||||
|
|
|
@ -305,6 +305,12 @@ export async function servicesFromKubernetes() {
|
|||
if (ingress.metadata.annotations[`${ANNOTATION_BASE}/statusStyle`]) {
|
||||
constructedService.statusStyle = ingress.metadata.annotations[`${ANNOTATION_BASE}/statusStyle`];
|
||||
}
|
||||
if (ingress.metadata.annotations[`${ANNOTATION_BASE}/allowUsers`]) {
|
||||
constructedService.allowUsers = ingress.metadata.annotations[`${ANNOTATION_BASE}/allowUsers`].split(",");
|
||||
}
|
||||
if (ingress.metadata.annotations[`${ANNOTATION_BASE}/allowGroups`]) {
|
||||
constructedService.allowGroups = ingress.metadata.annotations[`${ANNOTATION_BASE}/allowGroups`].split(",");
|
||||
}
|
||||
Object.keys(ingress.metadata.annotations).forEach((annotation) => {
|
||||
if (annotation.startsWith(ANNOTATION_WIDGET_BASE)) {
|
||||
shvl.set(
|
||||
|
|
70
src/utils/identity/identity-helpers.js
Normal file
70
src/utils/identity/identity-helpers.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import ProxyIdentityProvider from "./proxy";
|
||||
import NullIdentityProvider from "./null";
|
||||
|
||||
const IdentityProviders = {
|
||||
null: NullIdentityProvider,
|
||||
proxy: ProxyIdentityProvider,
|
||||
};
|
||||
|
||||
function getProviderByKey(key) {
|
||||
return IdentityProviders[key] || NullIdentityProvider;
|
||||
}
|
||||
|
||||
function identityAllow({ user, groups }, item) {
|
||||
const groupAllow =
|
||||
"allowGroups" in item && item.allowGroups && groups.some((group) => item.allowGroups.includes(group));
|
||||
const userAllow = "allowUsers" in item && item.allowUsers && item.allowUsers.includes(user);
|
||||
const allowAll = !("allowGroups" in item && item.allowGroups) && !("allowUsers" in item && item.allowUsers);
|
||||
|
||||
return userAllow || groupAllow || allowAll;
|
||||
}
|
||||
|
||||
export function checkAllowedGroup(perms, idGroups, groupName) {
|
||||
const testGroup = idGroups.find((group) => group.name === groupName);
|
||||
return testGroup ? identityAllow(perms, testGroup) : true;
|
||||
}
|
||||
|
||||
function filterAllowedItems(perms, idGroups, groups, groupKey) {
|
||||
return groups
|
||||
.filter((group) => checkAllowedGroup(perms, idGroups, group.name))
|
||||
.map((group) => ({
|
||||
name: group.name,
|
||||
[groupKey]: group[groupKey].filter((item) => identityAllow(perms, item)),
|
||||
}))
|
||||
.filter((group) => group[groupKey].length);
|
||||
}
|
||||
|
||||
export function readIdentitySettings({ provider, groups } = {}) {
|
||||
let groupArray = [];
|
||||
if (groups) {
|
||||
if (Array.isArray(groups)) {
|
||||
groupArray = groups.map((group) => ({
|
||||
name: Object.keys(group)[0],
|
||||
allowUsers: group.allowUsers,
|
||||
allowGroups: group.allowGroups,
|
||||
}));
|
||||
} else {
|
||||
groupArray = Object.keys(groups).map((group) => ({
|
||||
name: group,
|
||||
allowUsers: groups[group].allowUsers,
|
||||
allowGroups: groups[group].allowGroups,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
provider: provider ? getProviderByKey(provider.type).create(provider) : NullIdentityProvider.create(),
|
||||
groups: groupArray,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchWithIdentity(key, context) {
|
||||
return getProviderByKey(context.provider).fetch([key, context]);
|
||||
}
|
||||
|
||||
export const filterAllowedServices = (perms, idGroups, services) =>
|
||||
filterAllowedItems(perms, idGroups, services, "services");
|
||||
export const filterAllowedBookmarks = (perms, idGroups, bookmarks) =>
|
||||
filterAllowedItems(perms, idGroups, bookmarks, "bookmarks");
|
||||
export const filterAllowedWidgets = (perms, widgets) =>
|
||||
widgets.filter((widget) => identityAllow(perms, widget.options));
|
21
src/utils/identity/null.js
Normal file
21
src/utils/identity/null.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
const NullIdentity = { user: null, groups: [] };
|
||||
|
||||
function createNullIdentity() {
|
||||
return {
|
||||
getIdentity: () => NullIdentity,
|
||||
getContext: () => ({
|
||||
provider: "null",
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchNullIdentity([key]) {
|
||||
return fetch(key).then((res) => res.json());
|
||||
}
|
||||
|
||||
const NullIdentityProvider = {
|
||||
create: createNullIdentity,
|
||||
fetch: fetchNullIdentity,
|
||||
};
|
||||
|
||||
export default NullIdentityProvider;
|
34
src/utils/identity/proxy.js
Normal file
34
src/utils/identity/proxy.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
// 'proxy' identity provider 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 their identity.
|
||||
function getProxyPermissions(userHeader, groupHeader, groupSeparator, request) {
|
||||
const user =
|
||||
userHeader && request.headers[userHeader.toLowerCase()] ? request.headers[userHeader.toLowerCase()] : null;
|
||||
const groupsString =
|
||||
groupHeader && request.headers[groupHeader.toLowerCase()] ? request.headers[groupHeader.toLowerCase()] : "";
|
||||
|
||||
return { user, groups: groupsString ? groupsString.split(groupSeparator ?? "|").map((v) => v.trim()) : [] };
|
||||
}
|
||||
|
||||
function createProxyIdentity({ groupHeader, groupSeparator, userHeader }) {
|
||||
return {
|
||||
getContext: (request) => ({
|
||||
provider: "proxy",
|
||||
...(userHeader &&
|
||||
request.headers[userHeader] && { [userHeader.toLowerCase()]: request.headers[userHeader.toLowerCase()] }),
|
||||
...(groupHeader &&
|
||||
request.headers[groupHeader] && { [groupHeader.toLowerCase()]: request.headers[groupHeader.toLowerCase()] }),
|
||||
}),
|
||||
getIdentity: (request) => getProxyPermissions(userHeader, groupHeader, groupSeparator, request),
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchProxyIdentity([key, context]) {
|
||||
return fetch(key, { headers: context.headers }).then((res) => res.json());
|
||||
}
|
||||
|
||||
const ProxyIdentityProvider = {
|
||||
create: createProxyIdentity,
|
||||
fetch: fetchProxyIdentity,
|
||||
};
|
||||
|
||||
export default ProxyIdentityProvider;
|
Loading…
Add table
Add a link
Reference in a new issue