Skip to content

Commit

Permalink
feat: dynamically change the OIDCProvider configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
aldbr committed Nov 30, 2023
1 parent b9ce6ae commit eecdfb2
Show file tree
Hide file tree
Showing 21 changed files with 485 additions and 353 deletions.
56 changes: 28 additions & 28 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/app/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LoginForm } from "@/components/ui/LoginForm";
import { LoginForm } from "@/components/applications/LoginForm";

export default function Page() {
return <LoginForm />;
Expand Down
2 changes: 1 addition & 1 deletion src/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import Dashboard from "@/components/layout/Dashboard";
import { OIDCSecure } from "@/components/auth/OIDCUtils";
import { OIDCSecure } from "@/components/layout/OIDCSecure";

export default function DashboardLayout({
children,
Expand Down
6 changes: 3 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Inter } from "next/font/google";
import { OIDCProvider } from "@/components/auth/OIDCUtils";
import { OIDCConfigurationProvider } from "@/contexts/OIDCConfigurationProvider";
import { ThemeProvider } from "@/contexts/ThemeProvider";

const inter = Inter({ subsets: ["latin"] });
Expand All @@ -21,9 +21,9 @@ export default function RootLayout({
return (
<html lang="en">
<body className={inter.className}>
<OIDCProvider>
<OIDCConfigurationProvider>
<ThemeProvider>{children}</ThemeProvider>
</OIDCProvider>
</OIDCConfigurationProvider>
</body>
</html>
);
Expand Down
237 changes: 237 additions & 0 deletions src/components/applications/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
"use client";

import { useState, useEffect } from "react";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
import Autocomplete from "@mui/material/Autocomplete";
import TextField from "@mui/material/TextField";
import { useMetadata, Metadata } from "@/hooks/metadata";
import NextLink from "next/link";
import Image from "next/image";
import { CssBaseline, Stack } from "@mui/material";
import { useMUITheme } from "@/hooks/theme";
import { ThemeProvider as MUIThemeProvider } from "@mui/material/styles";
import { useRouter } from "next/navigation";
import { useOIDCContext } from "@/hooks/oidcConfiguration";
import * as React from "react";
import { useOidc } from "@axa-fr/react-oidc";
import { deepOrange, lightGreen } from "@mui/material/colors";

/**
* Login form
* @returns a form
*/
export function LoginForm() {
const theme = useMUITheme();
const router = useRouter();
const { data, error, isLoading } = useMetadata();
const [selectedVO, setSelectedVO] = useState<string | null>(null);
const [selectedGroup, setSelectedGroup] = useState<string | null>(null);
const {
configuration,
setConfiguration,
configurationName,
setConfigurationName,
} = useOIDCContext();
const { isAuthenticated, login } = useOidc(configurationName);

// Login if not authenticated
useEffect(() => {
if (configurationName && isAuthenticated === false) {
login();
}
}, [configurationName, isAuthenticated, login]);

// Get default group
const getDefaultGroup = (data: Metadata | undefined, vo: string): string => {
if (!data) {
return "";
}

const defaultGroup = data.virtual_organizations[vo]?.default_group;
if (defaultGroup) {
return defaultGroup;
} else {
const groupKeys = Object.keys(data.virtual_organizations[vo].groups);
return groupKeys.length > 0 ? groupKeys[0] : "";
}
};

// Set vo
const handleVOChange = (
event: React.SyntheticEvent,
newValue: string | null,
) => {
if (newValue) {
setSelectedVO(newValue);
setSelectedGroup(getDefaultGroup(data, newValue));
}
};

// Set group
const handleGroupChange = (event: SelectChangeEvent) => {
const value = event.target.value;
setSelectedGroup(value);
};

// Update OIDC configuration
const handleConfigurationChanges = () => {
if (selectedVO && selectedGroup && configuration) {
const newScope = `vo:${selectedVO} group:${selectedGroup}`;
setConfiguration({
...configuration,
scope: newScope,
});
setConfigurationName(newScope);

sessionStorage.setItem("oidcConfigName", JSON.stringify(newScope));
login();
}
};
// Redirect to dashboard if already authenticated
if (isAuthenticated) {
router.push("/dashboard");
return null;
}

if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>An error occurred while fetching metadata.</div>;
}
if (!data) {
return <div>No metadata found.</div>;
}

// Is there only one VO?
const singleVO = data && Object.keys(data.virtual_organizations).length === 1;
if (singleVO && !selectedVO) {
setSelectedVO(Object.keys(data.virtual_organizations)[0]);
setSelectedGroup(
getDefaultGroup(data, Object.keys(data.virtual_organizations)[0]),
);
}

return (
<React.Fragment>
<MUIThemeProvider theme={theme}>
<CssBaseline />

<Box
sx={{
ml: { xs: "5%", md: "30%" },
mr: { xs: "5%", md: "30%" },
}}
>
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
paddingTop: "10%",
paddingBottom: "10%",
}}
>
<NextLink href="/">
<Image
src="/DIRAC-logo-minimal.png"
alt="DIRAC logo"
width={150}
height={150}
/>
</NextLink>
</Box>
{singleVO ? (
<Typography
variant="h3"
gutterBottom
sx={{ textAlign: "center" }}
data-testid="h3-vo-name"
>
{selectedVO}
</Typography>
) : (
<Autocomplete
options={Object.keys(data.virtual_organizations)}
getOptionLabel={(option) => option}
renderInput={(params) => (
<TextField
{...params}
label="Select Virtual Organization"
variant="outlined"
data-testid="autocomplete-vo-select"
/>
)}
value={selectedVO}
onChange={handleVOChange}
sx={{
"& .MuiAutocomplete-root": {
// Style changes when an option is selected
opacity: selectedVO ? 0.5 : 1,
},
}}
/>
)}
{selectedVO && (
<Box sx={{ mt: 4 }}>
<FormControl fullWidth>
<InputLabel>Select a Group</InputLabel>
<Select
name={selectedVO}
value={
selectedGroup ||
data.virtual_organizations[selectedVO].default_group
}
label="Select a Group"
onChange={handleGroupChange}
data-testid="select-group"
>
{Object.keys(
data.virtual_organizations[selectedVO].groups,
).map((groupKey) => (
<MenuItem key={groupKey} value={groupKey}>
{groupKey}
</MenuItem>
))}
</Select>
</FormControl>
<Stack
direction={{ xs: "column", sm: "row" }}
spacing={2}
sx={{ mt: 5, width: "100%" }}
>
<Button
variant="contained"
sx={{
bgcolor: lightGreen[700],
"&:hover": { bgcolor: deepOrange[500] },
flexGrow: 1,
}}
onClick={handleConfigurationChanges}
data-testid="button-login"
>
Login through your Identity Provider
</Button>
<Button variant="outlined" onClick={() => {}}>
Advanced Options
</Button>
</Stack>
<Typography
sx={{ paddingTop: "5%", color: "gray", textAlign: "center" }}
>
Need help?{" "}
{data.virtual_organizations[selectedVO].support.message}
</Typography>
</Box>
)}
</Box>
</MUIThemeProvider>
</React.Fragment>
);
}
Loading

0 comments on commit eecdfb2

Please sign in to comment.