WIP: Implement SSO

This commit is contained in:
grimsi
2024-09-16 16:27:12 +02:00
parent a2870011e8
commit 9dd641656c
18 changed files with 244 additions and 34 deletions
@@ -77,6 +77,8 @@ export default function ProfileManagement() {
<Form>
<div className="flex flex-row flex-grow justify-between mb-8">
<h2 className="text-2xl font-bold">My Profile</h2>
{auth.state.user?.managedBySso &&
<p className="text-warning">Your account is managed externally.</p>}
<div className="flex flex-row items-center gap-4">
{formik.values.newPassword.length > 0 &&
@@ -88,7 +90,7 @@ export default function ProfileManagement() {
<Button
color="primary"
isLoading={formik.isSubmitting}
disabled={formik.isSubmitting || configSaved}
disabled={formik.isSubmitting || configSaved || auth.state.user?.managedBySso}
type="submit"
>
{formik.isSubmitting ? "" : configSaved ? <Check/> : "Save"}
@@ -105,24 +107,28 @@ export default function ProfileManagement() {
</Avatar>
</div>
<div className="flex flex-row gap-2">
<NextUiInput type="file" accept="image/*" onChange={onFileSelected}/>
<NextUiInput type="file" accept="image/*" onChange={onFileSelected}
isDisabled={auth.state.user?.managedBySso}/>
<Button onClick={() => uploadAvatar(avatar)} isDisabled={avatar == null}
color="success">Upload</Button>
<Tooltip content="Remove your current avatar">
<Button onClick={removeAvatar} isIconOnly color="danger"><Trash/></Button>
<Button onClick={removeAvatar} isIconOnly color="danger"
isDisabled={auth.state.user?.managedBySso}><Trash/></Button>
</Tooltip>
</div>
</div>
<div className="flex flex-col flex-grow">
<Section title="Personal information"/>
<Input name="username" label="Username" type="text" autocomplete="username"/>
<Input name="email" label="Email" type="email" autocomplete="email"/>
<Input name="username" label="Username" type="text" autocomplete="username"
isDisabled={auth.state.user?.managedBySso}/>
<Input name="email" label="Email" type="email" autocomplete="email"
isDisabled={auth.state.user?.managedBySso}/>
<Section title="Security"/>
<Input name="newPassword" label="New Password" type="password"
autocomplete="new-password"/>
autocomplete="new-password" isDisabled={auth.state.user?.managedBySso}/>
<Input name="passwordRepeat" label="Repeat password" type="password"
autocomplete="new-password"/>
autocomplete="new-password" isDisabled={auth.state.user?.managedBySso}/>
</div>
</div>
</Form>
@@ -1,11 +1,75 @@
import React from "react";
import withConfigPage from "Frontend/components/administration/withConfigPage";
import * as Yup from 'yup';
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
import Section from "Frontend/components/general/Section";
import {Button} from "@nextui-org/react";
import {MagicWand} from "@phosphor-icons/react";
import {toast} from "sonner";
function SsoMangementLayout({getConfig, formik}: any) {
function isAutoPopulateDisabled() {
return !formik.values.sso.oidc.enabled || !formik.values.sso.oidc["issuer-url"];
}
async function autoPopulate() {
let issuerUrl: string = formik.values.sso.oidc["issuer-url"];
if (issuerUrl.endsWith("/")) issuerUrl = issuerUrl.slice(0, -1);
try {
const response = await fetch(issuerUrl + "/.well-known/openid-configuration");
const data = await response.json();
formik.setFieldValue("sso.oidc.authorize-url", data.authorization_endpoint);
formik.setFieldValue("sso.oidc.token-url", data.token_endpoint);
formik.setFieldValue("sso.oidc.userinfo-url", data.userinfo_endpoint);
formik.setFieldValue("sso.oidc.logout-url", data.end_session_endpoint);
formik.setFieldValue("sso.oidc.jwks-url", data.jwks_uri);
} catch (e) {
toast.error("Failed to auto-populate SSO configuration");
}
}
return (
<div className="flex flex-col">
<div className="flex flex-row">
<div className="flex flex-col flex-1">
<ConfigFormField configElement={getConfig("sso.oidc.enabled")}/>
<Section title="SSO user handling"/>
<div className="flex flex-row">
<ConfigFormField configElement={getConfig("sso.oidc.auto-register-new-users")}
isDisabled={!formik.values.sso.oidc.enabled}/>
<ConfigFormField configElement={getConfig("sso.oidc.match-existing-users-by")}
isDisabled={!formik.values.sso.oidc.enabled}/>
</div>
<Section title="SSO provider configuration"/>
<ConfigFormField configElement={getConfig("sso.oidc.client-id")}
isDisabled={!formik.values.sso.oidc.enabled}/>
<ConfigFormField configElement={getConfig("sso.oidc.client-secret")}
isDisabled={!formik.values.sso.oidc.enabled}/>
<div className="flex flex-row gap-2">
<ConfigFormField configElement={getConfig("sso.oidc.issuer-url")}
isDisabled={!formik.values.sso.oidc.enabled}/>
<Button
isDisabled={isAutoPopulateDisabled()}
onPress={autoPopulate}
className="h-14 mt-2"><MagicWand/> Auto-populate</Button>
</div>
<ConfigFormField configElement={getConfig("sso.oidc.authorize-url")}
isDisabled={!formik.values.sso.oidc.enabled}/>
<ConfigFormField configElement={getConfig("sso.oidc.token-url")}
isDisabled={!formik.values.sso.oidc.enabled}/>
<ConfigFormField configElement={getConfig("sso.oidc.userinfo-url")}
isDisabled={!formik.values.sso.oidc.enabled}/>
<ConfigFormField configElement={getConfig("sso.oidc.logout-url")}
isDisabled={!formik.values.sso.oidc.enabled}/>
<ConfigFormField configElement={getConfig("sso.oidc.jwks-url")}
isDisabled={!formik.values.sso.oidc.enabled}/>
</div>
</div>
</div>
);
}
@@ -30,8 +30,7 @@ function UserManagementLayout({getConfig, formik}: any) {
{users.map((user) => <UserManagementCard user={user} key={user.username}/>)}
</div>
</div>
)
;
);
}
export const UserManagement = withConfigPage(UserManagementLayout, "User Management", "users");