feat: map info from OIDC claims to headers (#122)

* refactor: return all values from body in the providers

* refactor: only accept claims following the OIDC spec

* feat: map info from OIDC claims to headers

* feat: add support for required oauth groups

* fix: bot suggestions

* feat: get claims from github and google

* fix: close body correctly
This commit is contained in:
Stavros
2025-04-30 19:57:49 +03:00
committed by GitHub
parent f824b84787
commit a9e8bf89a9
24 changed files with 528 additions and 210 deletions

Binary file not shown.

4
frontend/src/index.css Normal file
View File

@@ -0,0 +1,4 @@
span,
p {
word-break: break-word;
}

View File

@@ -41,7 +41,8 @@
"totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",

View File

@@ -41,7 +41,8 @@
"totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",

View File

@@ -19,6 +19,7 @@ import { TotpPage } from "./pages/totp-page.tsx";
import { AppContextProvider } from "./context/app-context.tsx";
import "./lib/i18n/i18n.ts";
import { ForgotPasswordPage } from "./pages/forgot-password-page.tsx";
import "./index.css";
const queryClient = new QueryClient();
@@ -38,7 +39,10 @@ createRoot(document.getElementById("root")!).render(
<Route path="/continue" element={<ContinuePage />} />
<Route path="/unauthorized" element={<UnauthorizedPage />} />
<Route path="/error" element={<InternalServerError />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route
path="/forgot-password"
element={<ForgotPasswordPage />}
/>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>

View File

@@ -10,7 +10,7 @@ import { useAppContext } from "../context/app-context";
import { Trans, useTranslation } from "react-i18next";
export const LogoutPage = () => {
const { isLoggedIn, username, oauth, provider } = useUserContext();
const { isLoggedIn, oauth, provider, email, username } = useUserContext();
const { genericName } = useAppContext();
const { t } = useTranslation();
@@ -56,7 +56,7 @@ export const LogoutPage = () => {
values={{
provider:
provider === "generic" ? genericName : capitalize(provider),
username: username,
username: email,
}}
/>
) : (

View File

@@ -3,11 +3,13 @@ import { Layout } from "../components/layouts/layout";
import { Navigate } from "react-router";
import { isQueryValid } from "../utils/utils";
import { Trans, useTranslation } from "react-i18next";
import React from "react";
export const UnauthorizedPage = () => {
const queryString = window.location.search;
const params = new URLSearchParams(queryString);
const username = params.get("username") ?? "";
const groupErr = params.get("groupErr") ?? "";
const resource = params.get("resource") ?? "";
const { t } = useTranslation();
@@ -16,33 +18,54 @@ export const UnauthorizedPage = () => {
return <Navigate to="/" />;
}
if (isQueryValid(resource) && !isQueryValid(groupErr)) {
return (
<UnauthorizedLayout>
<Trans
i18nKey="unauthorizedResourceSubtitle"
t={t}
components={{ Code: <Code /> }}
values={{ resource, username }}
/>
</UnauthorizedLayout>
);
}
if (isQueryValid(groupErr) && isQueryValid(resource)) {
return (
<UnauthorizedLayout>
<Trans
i18nKey="unauthorizedGroupsSubtitle"
t={t}
components={{ Code: <Code /> }}
values={{ username, resource }}
/>
</UnauthorizedLayout>
);
}
return (
<UnauthorizedLayout>
<Trans
i18nKey="unauthorizedLoginSubtitle"
t={t}
components={{ Code: <Code /> }}
values={{ username }}
/>
</UnauthorizedLayout>
);
};
const UnauthorizedLayout = ({ children }: { children: React.ReactNode }) => {
const { t } = useTranslation();
return (
<Layout>
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
<Text size="xl" fw={700}>
{t("Unauthorized")}
</Text>
<Text>
{isQueryValid(resource) ? (
<Text>
<Trans
i18nKey="unauthorizedResourceSubtitle"
t={t}
components={{ Code: <Code /> }}
values={{ resource, username }}
/>
</Text>
) : (
<Text>
<Trans
i18nKey="unaothorizedLoginSubtitle"
t={t}
components={{ Code: <Code /> }}
values={{ username }}
/>
</Text>
)}
</Text>
<Text>{children}</Text>
<Button
fullWidth
mt="xl"

View File

@@ -3,6 +3,8 @@ import { z } from "zod";
export const userContextSchema = z.object({
isLoggedIn: z.boolean(),
username: z.string(),
name: z.string(),
email: z.string(),
oauth: z.boolean(),
provider: z.string(),
totpPending: z.boolean(),