mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-15 08:15:37 +00:00
WIP: Implement SSO
This commit is contained in:
@@ -2,11 +2,20 @@ import {useAuth} from "Frontend/util/auth";
|
||||
import {GearFine, Question, SignOut, User} from "@phosphor-icons/react";
|
||||
import {Avatar, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@nextui-org/react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {ConfigController} from "Frontend/generated/endpoints";
|
||||
|
||||
export default function ProfileMenu() {
|
||||
const auth = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function logout() {
|
||||
if (auth.state.user?.managedBySso) {
|
||||
window.location.href = await ConfigController.getLogoutUrl() || "/";
|
||||
} else {
|
||||
await auth.logout();
|
||||
}
|
||||
}
|
||||
|
||||
const profileMenuItems = [
|
||||
{
|
||||
label: "My Profile",
|
||||
@@ -27,7 +36,7 @@ export default function ProfileMenu() {
|
||||
{
|
||||
label: "Sign Out",
|
||||
icon: <SignOut/>,
|
||||
onClick: () => auth.logout(),
|
||||
onClick: logout,
|
||||
color: "primary"
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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");
|
||||
@@ -9,7 +9,7 @@ const Input = ({label, ...props}) => {
|
||||
const [field, meta] = useField(props);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-sm items-start gap-2 my-2">
|
||||
<div className="flex flex-col flex-grow items-start gap-2 my-2">
|
||||
<NextUiInput
|
||||
{...props}
|
||||
{...field}
|
||||
|
||||
Reference in New Issue
Block a user