Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Clients/src/application/config/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ForgotPassword from "../../presentation/pages/Authentication/ForgotPasswo
import ResetPassword from "../../presentation/pages/Authentication/ResetPassword";
import SetNewPassword from "../../presentation/pages/Authentication/SetNewPassword";
import ResetPasswordContinue from "../../presentation/pages/Authentication/ResetPasswordContinue";
import MicrosoftCallback from "../../presentation/pages/Authentication/MicrosoftCallback";
import FileManager from "../../presentation/pages/FileManager";
import Reporting from "../../presentation/pages/Reporting";
import VWHome from "../../presentation/pages/Home/1.0Home";
Expand Down Expand Up @@ -144,7 +145,12 @@ export const createRoutes = (
path="/reset-password-continue"
element={<ProtectedRoute Component={ResetPasswordContinue} />}
/>,
// <Route key="public" path="/public" element={<AITrustCentrePublic />} />,
<Route
key="microsoft-callback"
path="/auth/microsoft/callback"
element={<MicrosoftCallback />}
/>,
// <Route key="public" path="/public" element={<AITrustCentrePublic />} />,
<Route key="aiTrustCentrepublic" path="/aiTrustCentre/:hash" element={<AITrustCentrePublic />} />,
<Route key="sharedView" path="/shared/:resourceType/:token" element={<SharedView />} />,
// Style Guide - Development only
Expand Down
4 changes: 4 additions & 0 deletions Clients/src/application/constants/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ const allowedRoles = {
deleteDataset: ["Admin", "Editor"],
manageApiKeys: ["Admin"],
},
sso: {
view: ["Admin"],
manage: ["Admin"],
},
};

export default allowedRoles;
16 changes: 16 additions & 0 deletions Clients/src/application/repository/organization.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,19 @@ export async function checkOrganizationExists(): Promise<boolean> {
throw error;
}
}

/**
* Retrieves all organizations in the system (public endpoint for login).
*
* @returns {Promise<any[]>} List of all organizations.
* @throws Will throw an error if the request fails.
*/
export async function getAllOrganizations(): Promise<any[]> {
try {
const response = await apiServices.get("/organizations");
const data = response.data as { data?: any[] };
return data?.data ?? [];
} catch (error) {
throw error;
}
}
88 changes: 88 additions & 0 deletions Clients/src/application/repository/ssoConfig.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { GetRequestParams, RequestParams } from "../../domain/interfaces/i.requestParams";
import { apiServices } from "../../infrastructure/api/networkServices";

/**
* Retrieves SSO configuration for an organization.
*
* @param {GetRequestParams} params - The parameters for the request.
* @returns {Promise<any>} The SSO configuration data retrieved from the API.
* @throws Will throw an error if the request fails.
*/
export async function GetSsoConfig({
routeUrl,
signal,
responseType = "json",
}: GetRequestParams): Promise<any> {
try {
const response = await apiServices.get(routeUrl, {
signal,
responseType,
});
return response;
} catch (error) {
throw error;
}
}

/**
* Updates SSO configuration for an organization.
*
* @param {RequestParams} params - The parameters for updating the SSO configuration.
* @returns {Promise<any>} A promise that resolves to the updated SSO configuration data.
* @throws Will throw an error if the update operation fails.
*/
export async function UpdateSsoConfig({
routeUrl,
body,
}: RequestParams): Promise<any> {
try {
const response = await apiServices.put(routeUrl, body);
return response.data;
} catch (error) {
throw error;
}
}

/**
* Enables or disables SSO for an organization.
*
* @param {RequestParams} params - The parameters for enabling/disabling SSO.
* @returns {Promise<any>} A promise that resolves to the response data.
* @throws Will throw an error if the operation fails.
*/
export async function ToggleSsoStatus({
routeUrl,
body,
}: RequestParams): Promise<any> {
try {
const response = await apiServices.put(routeUrl, body);
return response.data;
} catch (error) {
throw error;
}
}

/**
* Check SSO status for a specific organization (public endpoint for login).
*
* @param {number} organizationId - The ID of the organization.
* @param {string} provider - The SSO provider (e.g., 'AzureAD').
* @returns {Promise<any>} A promise that resolves to SSO status data.
* @throws Will throw an error if the operation fails.
*/
export async function CheckSsoStatusByOrgId({
organizationId,
provider = 'AzureAD',
}: {
organizationId: number;
provider?: string;
}): Promise<any> {
try {
const response = await apiServices.get(
`ssoConfig/check-status?organizationId=${organizationId}&provider=${provider}`
);
return response.data;
} catch (error) {
throw error;
}
}
16 changes: 16 additions & 0 deletions Clients/src/application/repository/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,19 @@ export async function deleteUserProfilePhoto(userId: number | string): Promise<A
const response = await apiServices.delete<DeleteResponse>(`/users/${userId}/profile-photo`);
return response;
}

export async function loginUserWithMicrosoft({
code,
organizationId,
}: {
code: string;
organizationId: number;
}): Promise<any> {
try {
const response = await apiServices.post(`/users/login-microsoft`, { code, organizationId });
return response;
} catch (error) {
console.error("Error logging in with Microsoft:", error);
throw error;
}
}
2 changes: 2 additions & 0 deletions Clients/src/domain/types/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export type User = {
organization_id?: number; //organization association
pwd_set?: boolean; //password set flag (compatibility)
data?: any; //compatibility property for API responses
sso_provider?: string | null;
sso_user_id?: string | null;
}

export interface ApiResponse<T> {
Expand Down
6 changes: 6 additions & 0 deletions Clients/src/presentation/assets/icons/microsoft-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
168 changes: 168 additions & 0 deletions Clients/src/presentation/components/Inputs/SelectField/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* A customizable select field component that matches the Field component's styling.
* Provides consistent styling with text inputs for organization selection.
*/

import {
Select,
MenuItem,
Stack,
Typography,
useTheme,
SelectChangeEvent,
} from "@mui/material";
import { getInputStyles } from "../../../utils/inputStyles";

interface SelectFieldProps {
id?: string;
label?: string;
isRequired?: boolean;
isOptional?: boolean;
optionalLabel?: string;
placeholder?: string;
value: string | number;
onChange: (event: SelectChangeEvent<string | number>) => void;
options: Array<{ id: number | string; name: string }>;
error?: string;
disabled?: boolean;
width?: string | number;
sx?: any;
loading?: boolean;
}

const SelectField = ({
id,
label,
isRequired,
isOptional,
optionalLabel,
placeholder,
value,
onChange,
options,
error,
disabled,
width,
sx,
loading = false,
}: SelectFieldProps) => {
const theme = useTheme();

const rootSx = sx;

return (
<Stack
gap={theme.spacing(2)}
className="field field-select"
sx={{
...getInputStyles(theme, { hasError: !!error }),
width: width,
...(rootSx || {}),
}}
>
{label && (
<Typography
component="p"
variant="body1"
color={theme.palette.text.secondary}
fontWeight={500}
fontSize={"13px"}
sx={{ margin: 0, height: '22px' }}
>
{label}
{isRequired ? (
<Typography
component="span"
ml={theme.spacing(1)}
color={theme.palette.error.text}
>
*
</Typography>
) : (
""
)}
{isOptional ? (
<Typography
component="span"
fontSize="inherit"
fontWeight={400}
ml={theme.spacing(2)}
sx={{ opacity: 0.6 }}
>
{optionalLabel || "(optional)"}
</Typography>
) : (
""
)}
</Typography>
)}
<Select
id={id}
value={value || ''}
onChange={onChange}
disabled={disabled || loading}
displayEmpty
renderValue={(selected) => {
if (!selected || selected === '') {
return (
<Typography sx={{ color: theme.palette.text.disabled, fontSize: 13 }}>
{loading ? 'Loading organizations...' : placeholder || 'Select an option'}
</Typography>
);
}
const selectedOption = options.find((opt) => opt.id === selected);
return selectedOption ? selectedOption.name : '';
}}
sx={{
color: theme.palette.text.secondary,
fontSize: 13,
'& .MuiSelect-select': {
padding: '10px 12px',
minHeight: 'auto',
},
'& .MuiOutlinedInput-notchedOutline': {
borderColor: theme.palette.border.light,
},
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: theme.palette.border.dark,
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: theme.palette.primary.main,
borderWidth: 1,
},
'&.Mui-disabled': {
backgroundColor: theme.palette.background.accent,
},
}}
>
{options.length === 0 ? (
<MenuItem value="" disabled sx={{ fontSize: 13 }}>
{loading ? 'Loading organizations...' : 'No organizations available'}
</MenuItem>
) : (
options.map((option) => (
<MenuItem key={option.id} value={option.id} sx={{ fontSize: 13 }}>
{option.name}
</MenuItem>
))
)}
</Select>
{error && (
<Typography
component="span"
className="input-error"
color={theme.palette.status.error.text}
mt={theme.spacing(2)}
sx={{
opacity: 0.8,
fontSize: 11,
}}
>
{error}
</Typography>
)}
</Stack>
);
};

export default SelectField;
Loading
Loading