feat: Add option to disable local auth form
This commit is contained in:
parent
59dac15bdf
commit
770cc1dbb3
10 changed files with 68 additions and 11 deletions
|
@ -4,6 +4,7 @@
|
|||
package cli // import "miniflux.app/v2/internal/cli"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -225,6 +226,17 @@ func Parse() {
|
|||
return
|
||||
}
|
||||
|
||||
if config.Opts.DisableLocalAuth() {
|
||||
switch {
|
||||
case config.Opts.OAuth2Provider() == "" && config.Opts.AuthProxyHeader() == "":
|
||||
printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled but neither OAUTH2_PROVIDER nor AUTH_PROXY_HEADER is not set. Please enable at least one authentication source"))
|
||||
case config.Opts.OAuth2Provider() != "" && !config.Opts.IsOAuth2UserCreationAllowed():
|
||||
printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled and an OAUTH2_PROVIDER is configured, but OAUTH2_USER_CREATION is not enabled"))
|
||||
case config.Opts.AuthProxyHeader() != "" && !config.Opts.IsAuthProxyUserCreationAllowed():
|
||||
printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled and an AUTH_PROXY_HEADER is configured, but AUTH_PROXY_USER_CREATION is not enabled"))
|
||||
}
|
||||
}
|
||||
|
||||
startDaemon(store)
|
||||
}
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ const (
|
|||
defaultOAuth2RedirectURL = ""
|
||||
defaultOAuth2OidcDiscoveryEndpoint = ""
|
||||
defaultOAuth2Provider = ""
|
||||
defaultDisableLocalAuth = false
|
||||
defaultPocketConsumerKey = ""
|
||||
defaultHTTPClientTimeout = 20
|
||||
defaultHTTPClientMaxBodySize = 15
|
||||
|
@ -154,6 +155,7 @@ type Options struct {
|
|||
oauth2RedirectURL string
|
||||
oidcDiscoveryEndpoint string
|
||||
oauth2Provider string
|
||||
disableLocalAuth bool
|
||||
pocketConsumerKey string
|
||||
httpClientTimeout int
|
||||
httpClientMaxBodySize int64
|
||||
|
@ -231,6 +233,7 @@ func NewOptions() *Options {
|
|||
oauth2RedirectURL: defaultOAuth2RedirectURL,
|
||||
oidcDiscoveryEndpoint: defaultOAuth2OidcDiscoveryEndpoint,
|
||||
oauth2Provider: defaultOAuth2Provider,
|
||||
disableLocalAuth: defaultDisableLocalAuth,
|
||||
pocketConsumerKey: defaultPocketConsumerKey,
|
||||
httpClientTimeout: defaultHTTPClientTimeout,
|
||||
httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024,
|
||||
|
@ -456,6 +459,11 @@ func (o *Options) OAuth2Provider() string {
|
|||
return o.oauth2Provider
|
||||
}
|
||||
|
||||
// DisableLocalAUth returns true if the local user database should not be used to authenticate users
|
||||
func (o *Options) DisableLocalAuth() bool {
|
||||
return o.disableLocalAuth
|
||||
}
|
||||
|
||||
// HasHSTS returns true if HTTP Strict Transport Security is enabled.
|
||||
func (o *Options) HasHSTS() bool {
|
||||
return o.hsts
|
||||
|
@ -695,6 +703,7 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
|||
"OAUTH2_PROVIDER": o.oauth2Provider,
|
||||
"OAUTH2_REDIRECT_URL": o.oauth2RedirectURL,
|
||||
"OAUTH2_USER_CREATION": o.oauth2UserCreationAllowed,
|
||||
"DISABLE_LOCAL_AUTH": o.disableLocalAuth,
|
||||
"POCKET_CONSUMER_KEY": redactSecretValue(o.pocketConsumerKey, redactSecret),
|
||||
"POLLING_FREQUENCY": o.pollingFrequency,
|
||||
"FORCE_REFRESH_INTERVAL": o.forceRefreshInterval,
|
||||
|
|
|
@ -227,6 +227,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
|||
p.opts.oidcDiscoveryEndpoint = parseString(value, defaultOAuth2OidcDiscoveryEndpoint)
|
||||
case "OAUTH2_PROVIDER":
|
||||
p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider)
|
||||
case "DISABLE_LOCAL_AUTH":
|
||||
p.opts.disableLocalAuth = parseBool(value, defaultDisableLocalAuth)
|
||||
case "HTTP_CLIENT_TIMEOUT":
|
||||
p.opts.httpClientTimeout = parseInt(value, defaultHTTPClientTimeout)
|
||||
case "HTTP_CLIENT_MAX_BODY_SIZE":
|
||||
|
|
|
@ -32,13 +32,14 @@ type funcMap struct {
|
|||
// Map returns a map of template functions that are compiled during template parsing.
|
||||
func (f *funcMap) Map() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"formatFileSize": formatFileSize,
|
||||
"dict": dict,
|
||||
"hasKey": hasKey,
|
||||
"truncate": truncate,
|
||||
"isEmail": isEmail,
|
||||
"baseURL": config.Opts.BaseURL,
|
||||
"rootURL": config.Opts.RootURL,
|
||||
"formatFileSize": formatFileSize,
|
||||
"dict": dict,
|
||||
"hasKey": hasKey,
|
||||
"truncate": truncate,
|
||||
"isEmail": isEmail,
|
||||
"baseURL": config.Opts.BaseURL,
|
||||
"rootURL": config.Opts.RootURL,
|
||||
"disableLocalAuth": config.Opts.DisableLocalAuth,
|
||||
"hasOAuth2Provider": func(provider string) bool {
|
||||
return config.Opts.OAuth2Provider() == provider
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
{{ define "content"}}
|
||||
<section class="login-form">
|
||||
{{ if not disableLocalAuth }}
|
||||
<form action="{{ route "checkLogin" }}" method="post">
|
||||
<input type="hidden" name="csrf" value="{{ .csrf }}">
|
||||
|
||||
|
@ -22,6 +23,7 @@
|
|||
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.loading" }}">{{ t "action.login" }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ if .webAuthnEnabled }}
|
||||
<div class="webauthn">
|
||||
<div role="alert" class="alert alert-error hidden" id="webauthn-error">
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<div role="alert" class="alert alert-error">{{ .errorMessage }}</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if not disableLocalAuth }}
|
||||
<fieldset>
|
||||
<legend>{{ t "form.prefs.fieldset.authentication_settings" }}</legend>
|
||||
|
||||
|
@ -49,6 +50,7 @@
|
|||
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
{{ end }}
|
||||
|
||||
{{ if .webAuthnEnabled }}
|
||||
<fieldset>
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"miniflux.app/v2/internal/config"
|
||||
"miniflux.app/v2/internal/locale"
|
||||
"miniflux.app/v2/internal/model"
|
||||
)
|
||||
|
@ -86,7 +87,9 @@ func ExtractMarkAsReadBehavior(behavior MarkReadBehavior) (markReadOnView, markR
|
|||
|
||||
// Merge updates the fields of the given user.
|
||||
func (s *SettingsForm) Merge(user *model.User) *model.User {
|
||||
user.Username = s.Username
|
||||
if !config.Opts.DisableLocalAuth() {
|
||||
user.Username = s.Username
|
||||
}
|
||||
user.Theme = s.Theme
|
||||
user.Language = s.Language
|
||||
user.Timezone = s.Timezone
|
||||
|
@ -120,7 +123,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
|
|||
|
||||
// Validate makes sure the form values are valid.
|
||||
func (s *SettingsForm) Validate() *locale.LocalizedError {
|
||||
if s.Username == "" || s.Theme == "" || s.Language == "" || s.Timezone == "" || s.EntryDirection == "" || s.DisplayMode == "" || s.DefaultHomePage == "" {
|
||||
if (s.Username == "" && !config.Opts.DisableLocalAuth()) || s.Theme == "" || s.Language == "" || s.Timezone == "" || s.EntryDirection == "" || s.DisplayMode == "" || s.DefaultHomePage == "" {
|
||||
return locale.NewLocalizedError("error.settings_mandatory_fields")
|
||||
}
|
||||
|
||||
|
|
|
@ -21,9 +21,18 @@ import (
|
|||
func (h *handler) checkLogin(w http.ResponseWriter, r *http.Request) {
|
||||
clientIP := request.ClientIP(r)
|
||||
sess := session.New(h.store, request.SessionID(r))
|
||||
authForm := form.NewAuthForm(r)
|
||||
|
||||
view := view.New(h.tpl, r, sess)
|
||||
|
||||
if config.Opts.DisableLocalAuth() {
|
||||
slog.Warn("blocking local auth login attempt, local auth is disabled",
|
||||
slog.String("client_ip", clientIP),
|
||||
slog.String("user_agent", r.UserAgent()),
|
||||
)
|
||||
html.OK(w, r, view.Render("login"))
|
||||
return
|
||||
}
|
||||
|
||||
authForm := form.NewAuthForm(r)
|
||||
view.Set("errorMessage", locale.NewLocalizedError("error.bad_credentials").Translate(request.UserLanguage(r)))
|
||||
view.Set("form", authForm)
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"miniflux.app/v2/internal/config"
|
||||
"miniflux.app/v2/internal/http/request"
|
||||
"miniflux.app/v2/internal/http/response/html"
|
||||
"miniflux.app/v2/internal/http/route"
|
||||
|
@ -15,6 +16,14 @@ import (
|
|||
)
|
||||
|
||||
func (h *handler) oauth2Unlink(w http.ResponseWriter, r *http.Request) {
|
||||
if config.Opts.DisableLocalAuth() {
|
||||
slog.Warn("blocking oauth2 unlink attempt, local auth is disabled",
|
||||
slog.String("user_agent", r.UserAgent()),
|
||||
)
|
||||
html.Redirect(w, r, route.Path(h.router, "login"))
|
||||
return
|
||||
}
|
||||
|
||||
printer := locale.NewPrinter(request.UserLanguage(r))
|
||||
provider := request.RouteStringParam(r, "provider")
|
||||
if provider == "" {
|
||||
|
|
|
@ -447,6 +447,14 @@ Possible values are "google" or "oidc"\&.
|
|||
.br
|
||||
Default is empty\&.
|
||||
.TP
|
||||
.B DISABLE_LOCAL_AUTH
|
||||
Only use oauth2 for auth\&.
|
||||
.br
|
||||
When set to true, the username/password form is hidden from the login screen, and the
|
||||
options to change username/password or unlink oauth2 account are hidden from the settings page.
|
||||
.br
|
||||
Default is false\&.
|
||||
.TP
|
||||
.B OAUTH2_REDIRECT_URL
|
||||
OAuth2 redirect URL\&.
|
||||
.br
|
||||
|
|
Loading…
Reference in a new issue