Skip to content

Commit e1a68f7

Browse files
feat(auth): Allow to hide username / password login form when OAuth is enabled (#518)
* 🚀 Feature: Allow to hide username / password login form when OAuth is enabled * Hide “Sign in” password form * Disable routes related to password authentication * Change styling of OAuth provider buttons * Open OAuth page in same tab * Fix consistent usage of informal language in de-DE locale Fixes #489 Signed-off-by: Marvin A. Ruder <[email protected]> * fix: order of new config variables --------- Signed-off-by: Marvin A. Ruder <[email protected]> Co-authored-by: Elias Schneider <[email protected]>
1 parent 9d9cc7b commit e1a68f7

File tree

5 files changed

+89
-45
lines changed

5 files changed

+89
-45
lines changed

backend/prisma/seed/config.seed.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ const configVariables: ConfigVariables = {
7171
enableShareEmailRecipients: {
7272
type: "boolean",
7373
defaultValue: "false",
74-
7574
secret: false,
7675
},
7776
shareRecipientsSubject: {
@@ -148,6 +147,11 @@ const configVariables: ConfigVariables = {
148147
type: "boolean",
149148
defaultValue: "true",
150149
},
150+
"disablePassword": {
151+
type: "boolean",
152+
defaultValue: "false",
153+
secret: false,
154+
},
151155
"github-enabled": {
152156
type: "boolean",
153157
defaultValue: "false",
@@ -229,7 +233,7 @@ const configVariables: ConfigVariables = {
229233
defaultValue: "",
230234
obscured: true,
231235
},
232-
}
236+
},
233237
};
234238

235239
type ConfigVariables = {
@@ -281,12 +285,15 @@ async function seedConfigVariables() {
281285

282286
async function migrateConfigVariables() {
283287
const existingConfigVariables = await prisma.config.findMany();
288+
const orderMap: { [category: string]: number } = {};
284289

285290
for (const existingConfigVariable of existingConfigVariables) {
286291
const configVariable =
287292
configVariables[existingConfigVariable.category]?.[
288-
existingConfigVariable.name
293+
existingConfigVariable.name
289294
];
295+
296+
// Delete the config variable if it doesn't exist in the seed
290297
if (!configVariable) {
291298
await prisma.config.delete({
292299
where: {
@@ -297,15 +304,11 @@ async function migrateConfigVariables() {
297304
},
298305
});
299306

300-
// Update the config variable if the metadata changed
301-
} else if (
302-
JSON.stringify({
303-
...configVariable,
304-
name: existingConfigVariable.name,
305-
category: existingConfigVariable.category,
306-
value: existingConfigVariable.value,
307-
}) != JSON.stringify(existingConfigVariable)
308-
) {
307+
// Update the config variable if it exists in the seed
308+
} else {
309+
const variableOrder = Object.keys(
310+
configVariables[existingConfigVariable.category]
311+
).indexOf(existingConfigVariable.name);
309312
await prisma.config.update({
310313
where: {
311314
name_category: {
@@ -318,8 +321,10 @@ async function migrateConfigVariables() {
318321
name: existingConfigVariable.name,
319322
category: existingConfigVariable.category,
320323
value: existingConfigVariable.value,
324+
order: variableOrder,
321325
},
322326
});
327+
orderMap[existingConfigVariable.category] = variableOrder + 1;
323328
}
324329
}
325330
}

backend/src/auth/auth.service.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ export class AuthService {
6161
if (!dto.email && !dto.username)
6262
throw new BadRequestException("Email or username is required");
6363

64+
if (this.config.get("oauth.disablePassword"))
65+
throw new ForbiddenException("Password sign in is disabled");
66+
6467
const user = await this.prisma.user.findFirst({
6568
where: {
6669
OR: [{ email: dto.email }, { username: dto.username }],
@@ -94,6 +97,9 @@ export class AuthService {
9497
}
9598

9699
async requestResetPassword(email: string) {
100+
if (this.config.get("oauth.disablePassword"))
101+
throw new ForbiddenException("Password sign in is disabled");
102+
97103
const user = await this.prisma.user.findFirst({
98104
where: { email },
99105
include: { resetPasswordToken: true },
@@ -119,6 +125,9 @@ export class AuthService {
119125
}
120126

121127
async resetPassword(token: string, newPassword: string) {
128+
if (this.config.get("oauth.disablePassword"))
129+
throw new ForbiddenException("Password sign in is disabled");
130+
122131
const user = await this.prisma.user.findFirst({
123132
where: { resetPasswordToken: { token } },
124133
});

frontend/src/components/auth/SignInForm.tsx

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ import toast from "../../utils/toast.util";
2828
import { safeRedirectPath } from "../../utils/router.util";
2929

3030
const useStyles = createStyles((theme) => ({
31+
signInWith: {
32+
fontWeight: 500,
33+
"&:before": {
34+
content: "''",
35+
flex: 1,
36+
display: "block",
37+
},
38+
"&:after": {
39+
content: "''",
40+
flex: 1,
41+
display: "block",
42+
},
43+
},
3144
or: {
3245
"&:before": {
3346
content: "''",
@@ -128,49 +141,58 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
128141
</Text>
129142
)}
130143
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
131-
<form
132-
onSubmit={form.onSubmit((values) => {
133-
signIn(values.emailOrUsername, values.password);
134-
})}
135-
>
136-
<TextInput
137-
label={t("signin.input.email-or-username")}
138-
placeholder={t("signin.input.email-or-username.placeholder")}
139-
{...form.getInputProps("emailOrUsername")}
140-
/>
141-
<PasswordInput
142-
label={t("signin.input.password")}
143-
placeholder={t("signin.input.password.placeholder")}
144-
mt="md"
145-
{...form.getInputProps("password")}
146-
/>
147-
{config.get("smtp.enabled") && (
148-
<Group position="right" mt="xs">
149-
<Anchor component={Link} href="/auth/resetPassword" size="xs">
150-
<FormattedMessage id="resetPassword.title" />
151-
</Anchor>
152-
</Group>
153-
)}
154-
<Button fullWidth mt="xl" type="submit">
155-
<FormattedMessage id="signin.button.submit" />
156-
</Button>
157-
</form>
144+
{config.get("oauth.disablePassword") || (
145+
<form
146+
onSubmit={form.onSubmit((values) => {
147+
signIn(values.emailOrUsername, values.password);
148+
})}
149+
>
150+
<TextInput
151+
label={t("signin.input.email-or-username")}
152+
placeholder={t("signin.input.email-or-username.placeholder")}
153+
{...form.getInputProps("emailOrUsername")}
154+
/>
155+
<PasswordInput
156+
label={t("signin.input.password")}
157+
placeholder={t("signin.input.password.placeholder")}
158+
mt="md"
159+
{...form.getInputProps("password")}
160+
/>
161+
{config.get("smtp.enabled") && (
162+
<Group position="right" mt="xs">
163+
<Anchor component={Link} href="/auth/resetPassword" size="xs">
164+
<FormattedMessage id="resetPassword.title" />
165+
</Anchor>
166+
</Group>
167+
)}
168+
<Button fullWidth mt="xl" type="submit">
169+
<FormattedMessage id="signin.button.submit" />
170+
</Button>
171+
</form>
172+
)}
158173
{oauth.length > 0 && (
159-
<Stack mt="xl">
160-
<Group align="center" className={classes.or}>
161-
<Text>{t("signIn.oauth.or")}</Text>
162-
</Group>
174+
<Stack mt={config.get("oauth.disablePassword") ? undefined : "xl"}>
175+
{config.get("oauth.disablePassword") ? (
176+
<Group align="center" className={classes.signInWith}>
177+
<Text>{t("signIn.oauth.signInWith")}</Text>
178+
</Group>
179+
) : (
180+
<Group align="center" className={classes.or}>
181+
<Text>{t("signIn.oauth.or")}</Text>
182+
</Group>
183+
)}
163184
<Group position="center">
164185
{oauth.map((provider) => (
165186
<Button
166187
key={provider}
167188
component="a"
168-
target="_blank"
169189
title={t(`signIn.oauth.${provider}`)}
170190
href={getOAuthUrl(config.get("general.appUrl"), provider)}
171191
variant="light"
192+
fullWidth
172193
>
173194
{getOAuthIcon(provider)}
195+
{"\u2002" + t(`signIn.oauth.${provider}`)}
174196
</Button>
175197
))}
176198
</Group>

frontend/src/i18n/translations/de-DE.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default {
3434
"signIn.notify.totp-required.title": "Zwei-Faktor-Authentifizierung benötigt",
3535
"signIn.notify.totp-required.description": "Bitte füge deinen Zwei-Faktor-Authentifizierungscode ein",
3636
"signIn.oauth.or": "ODER",
37+
"signIn.oauth.signInWith": "Anmelden mit",
3738
"signIn.oauth.github": "GitHub",
3839
"signIn.oauth.google": "Google",
3940
"signIn.oauth.microsoft": "Microsoft",
@@ -348,6 +349,9 @@ export default {
348349
"admin.config.oauth.allow-registration.description": "Benutzern erlauben, sich über Soziale Netzwerke zu registrieren",
349350
"admin.config.oauth.ignore-totp": "TOTP ignorieren",
350351
"admin.config.oauth.ignore-totp.description": "Gibt an, ob TOTP ignoriert werden soll, wenn sich der Benutzer über Soziale Netzwerke anmeldet",
352+
"admin.config.oauth.disable-password": "Anmelden mit Passwort deaktivieren",
353+
"admin.config.oauth.disable-password.description":
354+
"Deaktiviert das Anmelden mit Passwort\nStelle vor Aktivierung dieser Konfiguration sicher, dass ein OAuth-Provider korrekt konfiguriert ist, um nicht ausgesperrt zu werden.",
351355
"admin.config.oauth.github-enabled": "GitHub",
352356
"admin.config.oauth.github-enabled.description": "GitHub Anmeldung erlaubt",
353357
"admin.config.oauth.github-client-id": "GitHub Client-ID",
@@ -401,7 +405,7 @@ export default {
401405
"error.msg.no_email": "Kann die E-Mail-Adresse von dem Konto {0} nicht abrufen.",
402406
"error.msg.already_linked": "Das Konto {0} ist bereits mit einem anderen Konto verknüpft.",
403407
"error.msg.not_linked": "Das Konto {0} wurde noch nicht mit einem Konto verknüpft.",
404-
"error.msg.unverified_account": "Dieses Konto {0} wurde noch nicht verifiziert, bitte versuchen Sie es nach der Verifikation erneut.",
408+
"error.msg.unverified_account": "Dieses Konto {0} wurde noch nicht verifiziert, bitte versuche es nach der Verifikation erneut.",
405409
"error.msg.discord_guild_permission_denied": "Du bist nicht berechtigt, Dich anzumelden.",
406410
"error.msg.cannot_get_user_info": "Deine Benutzerinformationen können nicht von diesem Konto {0} abgerufen werden.",
407411
"error.param.provider_github": "GitHub",

frontend/src/i18n/translations/en-US.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default {
4444
"signIn.notify.totp-required.description":
4545
"Please enter your two-factor authentication code",
4646
"signIn.oauth.or": "OR",
47+
"signIn.oauth.signInWith": "Sign in with",
4748
"signIn.oauth.github": "GitHub",
4849
"signIn.oauth.google": "Google",
4950
"signIn.oauth.microsoft": "Microsoft",
@@ -479,6 +480,9 @@ export default {
479480
"admin.config.oauth.ignore-totp": "Ignore TOTP",
480481
"admin.config.oauth.ignore-totp.description":
481482
"Whether to ignore TOTP when user using social login",
483+
"admin.config.oauth.disable-password": "Disable password login",
484+
"admin.config.oauth.disable-password.description":
485+
"Whether to disable password login\nMake sure that an OAuth provider is properly configured before activating this configuration to avoid being locked out.",
482486
"admin.config.oauth.github-enabled": "GitHub",
483487
"admin.config.oauth.github-enabled.description":
484488
"Whether GitHub login is enabled",

0 commit comments

Comments
 (0)