usememos/memos · critical · ErrUnsafeAuthenticationConfiguration

password authentication for regular users cannot be disabled

Error message

password authentication for regular users cannot be disabled without an effective identity provider

What it means

ErrUnsafeAuthenticationConfiguration is a sentinel thrown when a stored authentication mutation (e.g. disabling password login) would leave regular users with no way to sign in, because no effective identity provider remains. It is the guard behind UpsertInstanceGeneralSettingSafely and its transactional validation, preventing an instance from being locked out.

Source

Thrown at store/auth_config.go:16

package store

import (
	"context"
	"time"

	"github.com/pkg/errors"
	"google.golang.org/protobuf/encoding/protojson"

	storepb "github.com/usememos/memos/proto/gen/store"
)

const authenticationMutationMaxAttempts = 3

// ErrUnsafeAuthenticationConfiguration indicates a mutation would lock regular users out.
var ErrUnsafeAuthenticationConfiguration = errors.New("password authentication for regular users cannot be disabled without an effective identity provider")

// AuthenticationConfigState is the stored authentication configuration read inside a transaction.
type AuthenticationConfigState struct {
	GeneralSetting    *InstanceSetting
	IdentityProviders []*IdentityProvider
}

// AuthenticationConfigMutation validates and applies one stored authentication mutation atomically.
type AuthenticationConfigMutation struct {
	UpsertGeneralSetting     *InstanceSetting
	DeleteIdentityProviderID *int32
	Validate                 func(*AuthenticationConfigState) error
}

// UpsertInstanceGeneralSettingSafely validates and stores GENERAL as one serialized operation.
func (s *Store) UpsertInstanceGeneralSettingSafely(ctx context.Context, setting *storepb.InstanceSetting) (*storepb.InstanceSetting, error) {
	if setting == nil || setting.Key != storepb.InstanceSettingKey_GENERAL || setting.GetGeneralSetting() == nil {
		return nil, errors.New("GENERAL instance setting is required")

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Create and verify the identity provider first, then disable password authentication
  2. Compare against errors.Is(err, store.ErrUnsafeAuthenticationConfiguration) to present a clear admin-facing message
  3. Re-enable password login until at least one IdP is confirmed working

Example fix

// before
setting.GeneralSetting.DisallowPasswordLogin = true
_, err := s.UpsertInstanceGeneralSettingSafely(ctx, setting)
// after
// 1) upsert the OAuth2 IdP
_, err := s.UpsertIdentityProvider(ctx, idp)
// 2) then disable password login
setting.GeneralSetting.DisallowPasswordLogin = true
_, err = s.UpsertInstanceGeneralSettingSafely(ctx, setting)
Defensive patterns

Strategy: try-catch

Validate before calling

// Before disabling password auth, confirm at least one effective IdP exists:
state := currentState(ctx) // GeneralSetting + IdentityProviders
disabling := newSetting.DisallowPasswordLogin
hasIdP := len(state.IdentityProviders) > 0
if disabling && !hasIdP {
    return errors.New("configure an identity provider before disabling passwords")
}

Try / catch

_, err := s.UpsertInstanceGeneralSettingSafely(ctx, setting)
if errors.Is(err, store.ErrUnsafeAuthenticationConfiguration) {
    // keep password auth enabled; surface a clear message to the admin
    return echo.NewHTTPError(http.StatusConflict, err.Error())
}

Prevention

When it happens

Trigger: Setting disallow_password_login (or equivalent) in the GENERAL instance setting while the effective IdP list is empty, or deleting the last identity provider while password auth is already disabled.

Common situations: Admins disabling password auth expecting a configured OAuth provider to take over when it was never persisted or is inactive; CI/test instances flipping auth flags; sequential edits where the IdP creation silently failed earlier.

Understand the failure class

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/c492378324ff0276. Report an issue: GitHub.