allow to hide login form (#459)

use the `hide_login_form` parameter in the `auth` settings to configure this feature
This commit is contained in:
Christoph 2025-06-27 13:45:47 +02:00
parent f994700caf
commit dab19a123e
8 changed files with 55 additions and 8 deletions

View File

@ -76,6 +76,7 @@ auth:
webauthn: webauthn:
enabled: true enabled: true
min_password_length: 16 min_password_length: 16
hide_login_form: false
web: web:
listening_address: :8888 listening_address: :8888
@ -354,6 +355,12 @@ Some core authentication options are shared across all providers, while others a
The default admin password strength is also enforced by this setting. The default admin password strength is also enforced by this setting.
- **Important:** The password should be strong and secure. It is recommended to use a password with at least 16 characters, including uppercase and lowercase letters, numbers, and special characters. - **Important:** The password should be strong and secure. It is recommended to use a password with at least 16 characters, including uppercase and lowercase letters, numbers, and special characters.
### `hide_login_form`
- **Default:** `false`
- **Description:** If `true`, the login form is hidden and only the OIDC, OAuth, LDAP, or WebAuthn providers are shown. This is useful if you want to enforce a specific authentication method.
If no social login providers are configured, the login form is always shown, regardless of this setting.
- **Important:** You can still access the login form by adding the `?all` query parameter to the login URL (e.g. https://wg.portal/#/login?all).
--- ---
### OIDC ### OIDC

View File

@ -16,7 +16,10 @@ const password = ref("")
const usernameInvalid = computed(() => username.value === "") const usernameInvalid = computed(() => username.value === "")
const passwordInvalid = computed(() => password.value === "") const passwordInvalid = computed(() => password.value === "")
const disableLoginBtn = computed(() => username.value === "" || password.value === "" || loggingIn.value) const disableLoginBtn = computed(() => username.value === "" || password.value === "" || loggingIn.value)
const showLoginForm = computed(() => {
console.log(router.currentRoute.value.query)
return settings.Setting('LoginFormVisible') || router.currentRoute.value.query.hasOwnProperty('all');
});
onMounted(async () => { onMounted(async () => {
await settings.LoadSettings() await settings.LoadSettings()
@ -98,7 +101,7 @@ const externalLogin = function (provider) {
</div></div> </div></div>
<div class="card-body"> <div class="card-body">
<form method="post"> <form method="post">
<fieldset> <fieldset v-if="showLoginForm">
<div class="form-group"> <div class="form-group">
<label class="form-label" for="inputUsername">{{ $t('login.username.label') }}</label> <label class="form-label" for="inputUsername">{{ $t('login.username.label') }}</label>
<div class="input-group mb-3"> <div class="input-group mb-3">
@ -118,19 +121,40 @@ const externalLogin = function (provider) {
</div> </div>
<div class="row mt-5 mb-2"> <div class="row mt-5 mb-2">
<div class="col-lg-4"> <div class="col-sm-4 col-xs-12">
<button :disabled="disableLoginBtn" class="btn btn-primary" type="submit" @click.prevent="login"> <button :disabled="disableLoginBtn" class="btn btn-primary mb-2" type="submit" @click.prevent="login">
{{ $t('login.button') }} <div v-if="loggingIn" class="d-inline"><i class="ms-2 fa-solid fa-circle-notch fa-spin"></i></div> {{ $t('login.button') }} <div v-if="loggingIn" class="d-inline"><i class="ms-2 fa-solid fa-circle-notch fa-spin"></i></div>
</button> </button>
</div> </div>
<div class="col-lg-8 mb-2 text-end"> <div class="col-sm-8 col-xs-12 text-sm-end">
<button v-if="settings.Setting('WebAuthnEnabled')" class="btn btn-primary" type="submit" @click.prevent="loginWebAuthn"> <button v-if="settings.Setting('WebAuthnEnabled')" class="btn btn-primary" type="submit" @click.prevent="loginWebAuthn">
{{ $t('login.button-webauthn') }} <div v-if="loggingIn" class="d-inline"><i class="ms-2 fa-solid fa-circle-notch fa-spin"></i></div> {{ $t('login.button-webauthn') }} <div v-if="loggingIn" class="d-inline"><i class="ms-2 fa-solid fa-circle-notch fa-spin"></i></div>
</button> </button>
</div> </div>
</div> </div>
<div class="row mt-5 d-flex"> <div class="row mt-4 d-flex">
<div class="col-lg-12 d-flex mb-2">
<!-- OpenIdConnect / OAUTH providers -->
<button v-for="(provider, idx) in auth.LoginProviders" :key="provider.Identifier" :class="{'ms-1':idx > 0}"
:disabled="loggingIn" :title="provider.Name" class="btn btn-outline-primary flex-fill"
v-html="provider.Name" @click.prevent="externalLogin(provider)"></button>
</div>
</div>
<div class="mt-3">
</div>
</fieldset>
<fieldset v-else>
<div class="row mt-1 mb-2" v-if="settings.Setting('WebAuthnEnabled')">
<div class="col-lg-12 d-flex mb-2">
<button class="btn btn-outline-primary flex-fill" type="submit" @click.prevent="loginWebAuthn">
{{ $t('login.button-webauthn') }} <div v-if="loggingIn" class="d-inline"><i class="ms-2 fa-solid fa-circle-notch fa-spin"></i></div>
</button>
</div>
</div>
<div class="row mt-1 d-flex">
<div class="col-lg-12 d-flex mb-2"> <div class="col-lg-12 d-flex mb-2">
<!-- OpenIdConnect / OAUTH providers --> <!-- OpenIdConnect / OAUTH providers -->
<button v-for="(provider, idx) in auth.LoginProviders" :key="provider.Identifier" :class="{'ms-1':idx > 0}" <button v-for="(provider, idx) in auth.LoginProviders" :key="provider.Identifier" :class="{'ms-1':idx > 0}"
@ -144,7 +168,6 @@ const externalLogin = function (provider) {
</fieldset> </fieldset>
</form> </form>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2231,6 +2231,9 @@
"ApiAdminOnly": { "ApiAdminOnly": {
"type": "boolean" "type": "boolean"
}, },
"LoginFormVisible": {
"type": "boolean"
},
"MailLinkOnly": { "MailLinkOnly": {
"type": "boolean" "type": "boolean"
}, },

View File

@ -381,6 +381,8 @@ definitions:
properties: properties:
ApiAdminOnly: ApiAdminOnly:
type: boolean type: boolean
LoginFormVisible:
type: boolean
MailLinkOnly: MailLinkOnly:
type: boolean type: boolean
MinPasswordLength: MinPasswordLength:

View File

@ -96,10 +96,13 @@ func (e ConfigEndpoint) handleSettingsGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
sessionUser := domain.GetUserInfo(r.Context()) sessionUser := domain.GetUserInfo(r.Context())
hasSocialLogin := len(e.cfg.Auth.OAuth) > 0 || len(e.cfg.Auth.OpenIDConnect) > 0 || e.cfg.Auth.WebAuthn.Enabled
// For anonymous users, we return the settings object with minimal information // For anonymous users, we return the settings object with minimal information
if sessionUser.Id == domain.CtxUnknownUserId || sessionUser.Id == "" { if sessionUser.Id == domain.CtxUnknownUserId || sessionUser.Id == "" {
respond.JSON(w, http.StatusOK, model.Settings{ respond.JSON(w, http.StatusOK, model.Settings{
WebAuthnEnabled: e.cfg.Auth.WebAuthn.Enabled, WebAuthnEnabled: e.cfg.Auth.WebAuthn.Enabled,
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
}) })
} else { } else {
respond.JSON(w, http.StatusOK, model.Settings{ respond.JSON(w, http.StatusOK, model.Settings{
@ -109,6 +112,7 @@ func (e ConfigEndpoint) handleSettingsGet() http.HandlerFunc {
ApiAdminOnly: e.cfg.Advanced.ApiAdminOnly, ApiAdminOnly: e.cfg.Advanced.ApiAdminOnly,
WebAuthnEnabled: e.cfg.Auth.WebAuthn.Enabled, WebAuthnEnabled: e.cfg.Auth.WebAuthn.Enabled,
MinPasswordLength: e.cfg.Auth.MinPasswordLength, MinPasswordLength: e.cfg.Auth.MinPasswordLength,
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
}) })
} }
} }

View File

@ -12,4 +12,5 @@ type Settings struct {
ApiAdminOnly bool `json:"ApiAdminOnly"` ApiAdminOnly bool `json:"ApiAdminOnly"`
WebAuthnEnabled bool `json:"WebAuthnEnabled"` WebAuthnEnabled bool `json:"WebAuthnEnabled"`
MinPasswordLength int `json:"MinPasswordLength"` MinPasswordLength int `json:"MinPasswordLength"`
LoginFormVisible bool `json:"LoginFormVisible"`
} }

View File

@ -21,6 +21,9 @@ type Auth struct {
// MinPasswordLength is the minimum password length for user accounts. This also applies to the admin user. // MinPasswordLength is the minimum password length for user accounts. This also applies to the admin user.
// It is encouraged to set this value to at least 16 characters. // It is encouraged to set this value to at least 16 characters.
MinPasswordLength int `yaml:"min_password_length"` MinPasswordLength int `yaml:"min_password_length"`
// HideLoginForm specifies whether the login form should be hidden. If no social login providers are configured,
// the login form will be shown regardless of this setting.
HideLoginForm bool `yaml:"hide_login_form"`
} }
// BaseFields contains the basic fields that are used to map user information from the authentication providers. // BaseFields contains the basic fields that are used to map user information from the authentication providers.

View File

@ -95,6 +95,9 @@ func (c *Config) LogStartupValues() {
"oidcProviders", len(c.Auth.OpenIDConnect), "oidcProviders", len(c.Auth.OpenIDConnect),
"oauthProviders", len(c.Auth.OAuth), "oauthProviders", len(c.Auth.OAuth),
"ldapProviders", len(c.Auth.Ldap), "ldapProviders", len(c.Auth.Ldap),
"webauthnEnabled", c.Auth.WebAuthn.Enabled,
"minPasswordLength", c.Auth.MinPasswordLength,
"hideLoginForm", c.Auth.HideLoginForm,
) )
} }
@ -169,6 +172,7 @@ func defaultConfig() *Config {
cfg.Auth.WebAuthn.Enabled = true cfg.Auth.WebAuthn.Enabled = true
cfg.Auth.MinPasswordLength = 16 cfg.Auth.MinPasswordLength = 16
cfg.Auth.HideLoginForm = false
return cfg return cfg
} }