Skip to content
This repository was archived by the owner on Jun 29, 2025. It is now read-only.
Open
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
12 changes: 12 additions & 0 deletions backend/prisma/seed/config.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,18 @@ export const configVariables = {
defaultValue: "",
obscured: true,
},
"microsoft-usernameClaim": {
type: "string",
defaultValue: "",
},
"microsoft-roleGeneralAccess": {
type: "string",
defaultValue: "",
},
"microsoft-roleAdminAccess": {
type: "string",
defaultValue: "",
},
"discord-enabled": {
type: "boolean",
defaultValue: "false",
Expand Down
23 changes: 22 additions & 1 deletion backend/src/oauth/provider/microsoft.provider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { GenericOidcProvider } from "./genericOidc.provider";
import { GenericOidcProvider, OidcToken } from "./genericOidc.provider";
import { ConfigService } from "../../config/config.service";
import { JwtService } from "@nestjs/jwt";
import { Inject, Injectable } from "@nestjs/common";
import { CACHE_MANAGER } from "@nestjs/cache-manager";
import { Cache } from "cache-manager";
import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
import { OAuthSignInDto } from "../dto/oauthSignIn.dto";
import { OAuthToken } from "./oauthProvider.interface";

@Injectable()
export class MicrosoftProvider extends GenericOidcProvider {
Expand All @@ -26,4 +29,22 @@ export class MicrosoftProvider extends GenericOidcProvider {
"oauth.microsoft-tenant",
)}/v2.0/.well-known/openid-configuration`;
}

getUserInfo(
token: OAuthToken<OidcToken>,
query: OAuthCallbackDto,
_?: string,
): Promise<OAuthSignInDto> {
const claim = this.config.get("oauth.microsoft-usernameClaim") || undefined;
const rolePath = "roles";
const roleGeneralAccess =
this.config.get("oauth.microsoft-roleGeneralAccess") || undefined;
const roleAdminAccess =
this.config.get("oauth.microsoft-roleAdminAccess") || undefined;
return super.getUserInfo(token, query, claim, {
path: rolePath,
generalAccess: roleGeneralAccess,
adminAccess: roleAdminAccess,
});
}
}
6 changes: 6 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ oauth:
microsoft-clientId: ""
#Client secret of the Microsoft OAuth app
microsoft-clientSecret: ""
#Username claim in Microsoft token. Leave it blank if you don’t know what this config is.
microsoft-usernameClaim: ""
#Role required for general access. Must be present in a user’s roles for them to log in. Leave it blank if you don't know what this config is.
microsoft-roleGeneralAccess: ""
#Role required for administrative access. Must be present in a user’s roles for them to access the admin panel. Leave it blank if you don't know what this config is.
microsoft-roleAdminAccess: ""
#Whether Discord login is enabled
discord-enabled: "false"
#Limit signing in to users in a specific server. Leave it blank to disable.
Expand Down
26 changes: 26 additions & 0 deletions docs/docs/setup/oauth2login.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@ Please follow the [official guide](https://docs.microsoft.com/en-us/azure/active

Redirect URL: `https://<your-domain>/api/oauth/callback/microsoft`

#### Roles configuration

Roles can be added to EntraID applications to match roles in the app in order to grant admin and/or general access.

In EntraID, go to the app registration previously created and do the following:

- Go to "App roles"
- Click "Create app role"
- Enter a display name for the role
- Select "Users/Groups" in "Allowed member types"
- Enter a value (this will be the actual value of the role)
- Add a description
- Enable the role

Then go to Enterprise applications:

- Select "Users and groups"
- Click "Add user/group"
- Select the users you want to assign the role to
- Select the role (if there are multiple roles)
- Click "Assign"

The token sent by EntraID will now include a "roles" field containing the value of the role linked to the user.

You can now select the desired role for "General Access" and "Admin Access" (using the EntraID role value) in the admin panel.

### Discord

Create an application on [Discord Developer Portal](https://discord.com/developers/applications).
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/i18n/translations/fr-FR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,24 @@ export default {
"admin.config.oauth.microsoft-client-id.description": "L’ID du client de l’application Microsoft OAuth",
"admin.config.oauth.microsoft-client-secret": "Secret du client Microsoft",
"admin.config.oauth.microsoft-client-secret.description": "Le secret du client de l’application Microsoft OAuth",
"admin.config.oauth.microsoft-username-claim": "Revendication du nom d’utilisateur Microsoft",
"admin.config.oauth.microsoft-username-claim.description": "Le champ contenant la revendication du nom d’utilisateur dans le jeton Microsft. Laissez vide si vous ne savez pas quoi indiquer.",
"admin.config.oauth.microsoft-role-general-access.description": "Rôle requis pour un accès général. Doit être présent dans les rôles d'un utilisateur pour qu'il se connecte. " + "Laissez vide si vous ne savez pas ce qu'est cette configuration.",
"admin.config.oauth.microsoft-role-admin-access": "Rôle Microsoft pour l'accès admin",
"admin.config.oauth.microsoft-role-admin-access.description": "Rôle requis pour l'accès administratif. Doit être présent dans les rôles d'un utilisateur pour accéder au panneau d'administration. " + "Laissez vide si vous ne savez pas ce qu'est cette configuration.",
"admin.config.group.general": "Général",
"admin.config.group.general.description": "Configuration générale",
"admin.config.group.github": "GitHub",
"admin.config.group.github.description": "Configuration de l'authentification GitHub",
"admin.config.group.google": "Google",
"admin.config.group.google.description": "Configuration de l'authentification Google",
"admin.config.group.microsoft": "Microsoft",
"admin.config.group.microsoft.description": "Configuration de l'authentification Microsoft",
"admin.config.group.discord": "Discord",
"admin.config.group.discord.description": "Configuration de l'authentification Discord",
"admin.config.group.oidc": "OpenID",
"admin.config.group.oidc.description": "Configuration de l'authentification OpenID",
"admin.config.oauth.microsoft-role-general-access": "Rôle Microsoft pour un accès général",
"admin.config.oauth.discord-enabled": "Discord",
"admin.config.oauth.discord-enabled.description": "Permettre la connexion via Discord",
"admin.config.oauth.discord-limited-users": "Utilisateurs limités sur Discord",
Expand Down
222 changes: 185 additions & 37 deletions frontend/src/pages/admin/config/[category].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Box,
Button,
Container,
Divider,
Group,
Stack,
Text,
Expand Down Expand Up @@ -152,46 +153,193 @@ export default function AppShellDemo() {
<Title mb="md" order={3}>
{t("admin.config.category." + categoryId)}
</Title>
{configVariables.map((configVariable) => (
<Group key={configVariable.key} position="apart">
<Stack
style={{ maxWidth: isMobile ? "100%" : "40%" }}
spacing={0}
{categoryId === "oauth" ? (
<>
{/* General OAuth Settings */}
<Box
mb="xl"
sx={{
padding: "1rem",
}}
>
<Title order={6}>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key,
)}`}
/>
</Title>

<Text
sx={{
whiteSpace: "pre-line",
}}
color="dimmed"
size="sm"
mb="xs"
>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key,
)}.description`}
values={{ br: <br /> }}
/>
</Text>
</Stack>
<Stack></Stack>
<Box style={{ width: isMobile ? "100%" : "50%" }}>
<AdminConfigInput
key={configVariable.key}
configVariable={configVariable}
updateConfigVariable={updateConfigVariable}
<Divider
my="xs"
label={
<Title order={4}>
{t("admin.config.group.general")}
</Title>
}
/>
<Text color="dimmed" size="sm" mb="md">
{t("admin.config.group.general.description")}
</Text>
{configVariables
.filter(
(config) =>
config.key.startsWith("oauth.") &&
![
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the OIDC list into a variable.

"github",
"google",
"microsoft",
"discord",
"oidc",
].some((provider) => config.key.includes(provider)),
)
.map((configVariable) => (
<Group key={configVariable.key} position="apart">
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the code for the configuration variables that contains the title, description and input into a separate component. Currently this code is there twice.

<Stack
style={{ maxWidth: isMobile ? "100%" : "40%" }}
spacing={0}
>
<Title order={6}>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key,
)}`}
/>
</Title>

<Text
sx={{
whiteSpace: "pre-line",
}}
color="dimmed"
size="sm"
mb="xs"
>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key,
)}.description`}
values={{ br: <br /> }}
/>
</Text>
</Stack>
<Stack></Stack>
<Box style={{ width: isMobile ? "100%" : "50%" }}>
<AdminConfigInput
key={configVariable.key}
configVariable={configVariable}
updateConfigVariable={updateConfigVariable}
/>
</Box>
</Group>
))}
</Box>
</Group>
))}

{/* OAuth Providers */}
{["github", "google", "microsoft", "discord", "oidc"].map(
(provider) => (
<Box
key={provider}
mb="xl"
sx={{
padding: "1rem",
}}
>
<Divider
my="xs"
label={
<Title order={4}>
{provider.charAt(0).toUpperCase() +
provider.slice(1)}
</Title>
}
/>
{configVariables
.filter((config) =>
config.key.startsWith(`oauth.${provider}`),
)
.map((configVariable) => (
<Group key={configVariable.key} position="apart">
<Stack
style={{
maxWidth: isMobile ? "100%" : "40%",
}}
spacing={0}
>
<Title order={6}>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key,
)}`}
/>
</Title>

<Text
sx={{
whiteSpace: "pre-line",
}}
color="dimmed"
size="sm"
mb="xs"
>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key,
)}.description`}
values={{ br: <br /> }}
/>
</Text>
</Stack>
<Stack></Stack>
<Box
style={{ width: isMobile ? "100%" : "50%" }}
>
<AdminConfigInput
key={configVariable.key}
configVariable={configVariable}
updateConfigVariable={updateConfigVariable}
/>
</Box>
</Group>
))}
</Box>
),
)}
</>
) : (
configVariables.map((configVariable) => (
<Group key={configVariable.key} position="apart">
<Stack
style={{ maxWidth: isMobile ? "100%" : "40%" }}
spacing={0}
>
<Title order={6}>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key,
)}`}
/>
</Title>

<Text
sx={{
whiteSpace: "pre-line",
}}
color="dimmed"
size="sm"
mb="xs"
>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key,
)}.description`}
values={{ br: <br /> }}
/>
</Text>
</Stack>
<Stack></Stack>
<Box style={{ width: isMobile ? "100%" : "50%" }}>
<AdminConfigInput
key={configVariable.key}
configVariable={configVariable}
updateConfigVariable={updateConfigVariable}
/>
</Box>
</Group>
))
)}
{categoryId == "general" && (
<LogoConfigInput logo={logo} setLogo={setLogo} />
)}
Expand Down