{"record":{"id":"c492378324ff0276","repo":"usememos/memos","slug":"password-authentication-for-regular-users-cannot-b","errorCode":null,"errorMessage":"password authentication for regular users cannot be disabled without an effective identity provider","messagePattern":"password authentication for regular users cannot be disabled without an effective identity provider","errorType":"validation","errorClass":"ErrUnsafeAuthenticationConfiguration","httpStatus":400,"severity":"critical","filePath":"store/auth_config.go","lineNumber":16,"sourceCode":"package store\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"google.golang.org/protobuf/encoding/protojson\"\n\n\tstorepb \"github.com/usememos/memos/proto/gen/store\"\n)\n\nconst authenticationMutationMaxAttempts = 3\n\n// ErrUnsafeAuthenticationConfiguration indicates a mutation would lock regular users out.\nvar ErrUnsafeAuthenticationConfiguration = errors.New(\"password authentication for regular users cannot be disabled without an effective identity provider\")\n\n// AuthenticationConfigState is the stored authentication configuration read inside a transaction.\ntype AuthenticationConfigState struct {\n\tGeneralSetting    *InstanceSetting\n\tIdentityProviders []*IdentityProvider\n}\n\n// AuthenticationConfigMutation validates and applies one stored authentication mutation atomically.\ntype AuthenticationConfigMutation struct {\n\tUpsertGeneralSetting     *InstanceSetting\n\tDeleteIdentityProviderID *int32\n\tValidate                 func(*AuthenticationConfigState) error\n}\n\n// UpsertInstanceGeneralSettingSafely validates and stores GENERAL as one serialized operation.\nfunc (s *Store) UpsertInstanceGeneralSettingSafely(ctx context.Context, setting *storepb.InstanceSetting) (*storepb.InstanceSetting, error) {\n\tif setting == nil || setting.Key != storepb.InstanceSettingKey_GENERAL || setting.GetGeneralSetting() == nil {\n\t\treturn nil, errors.New(\"GENERAL instance setting is required\")","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/store/auth_config.go#L1-L34","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Create and verify the identity provider first, then disable password authentication","Compare against errors.Is(err, store.ErrUnsafeAuthenticationConfiguration) to present a clear admin-facing message","Re-enable password login until at least one IdP is confirmed working"],"exampleFix":"// before\nsetting.GeneralSetting.DisallowPasswordLogin = true\n_, err := s.UpsertInstanceGeneralSettingSafely(ctx, setting)\n// after\n// 1) upsert the OAuth2 IdP\n_, err := s.UpsertIdentityProvider(ctx, idp)\n// 2) then disable password login\nsetting.GeneralSetting.DisallowPasswordLogin = true\n_, err = s.UpsertInstanceGeneralSettingSafely(ctx, setting)","handlingStrategy":"try-catch","validationCode":"// Before disabling password auth, confirm at least one effective IdP exists:\nstate := currentState(ctx) // GeneralSetting + IdentityProviders\ndisabling := newSetting.DisallowPasswordLogin\nhasIdP := len(state.IdentityProviders) > 0\nif disabling && !hasIdP {\n    return errors.New(\"configure an identity provider before disabling passwords\")\n}","typeGuard":null,"tryCatchPattern":"_, err := s.UpsertInstanceGeneralSettingSafely(ctx, setting)\nif errors.Is(err, store.ErrUnsafeAuthenticationConfiguration) {\n    // keep password auth enabled; surface a clear message to the admin\n    return echo.NewHTTPError(http.StatusConflict, err.Error())\n}","preventionTips":["Order auth changes: add and verify an IdP first, then disable passwords","Always compare with errors.Is against the sentinel, never string matching"],"tags":["store","auth","configuration","lockout"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}