diff --git a/.golangci.yml b/.golangci.yml
index b189b069cfac06a44ad03bf3362f6871aa460233..f4d86fdc5d5a8bf8af3412341fdca18bc491359b 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -69,11 +69,14 @@ linters-settings:
     exclude-functions:
       - fmt.Fprintf
       - os.Remove
+      - os.RemoveAll
       - "(io.ReadCloser).Close"
       - "(*os.File).Close"
+      - "(net.Listener).Close"
       - "(*database/sql.Rows).Close"
       - "(*database/sql.Tx).Rollback"
       - "(*database/sql.DB).Close"
+      - "(*google.golang.org/grpc.ClientConn).Close"
 
   # Disable error checking, as errorcheck detects more errors and is more configurable.
   gosec:
@@ -123,7 +126,7 @@ linters-settings:
     suggest-new: true
   dupl:
     # tokens count to trigger issue, 150 by default
-    threshold: 100
+    threshold: 150
   goconst:
     # minimal length of string constant, 3 by default
     min-len: 3
diff --git a/authn/auth.go b/authn/auth.go
index 2ab0902c84de3679527386f5dc15693c95fd25bb..67dd20abe990982b44ad115fa28035f28ed8c167 100644
--- a/authn/auth.go
+++ b/authn/auth.go
@@ -2,12 +2,11 @@ package authn
 
 import (
 	"context"
-	"net"
 	"time"
 
 	authpb "git.autistici.org/smol/idp/authn/proto"
-	"git.autistici.org/smol/idp/userdb"
 	pb "git.autistici.org/smol/idp/proto"
+	"git.autistici.org/smol/idp/userdb"
 )
 
 // Custom authpb.Response that includes a private error diagnostic
@@ -58,16 +57,6 @@ type shortTermStorage interface {
 	Set(context.Context, []byte, []byte, time.Duration) error
 }
 
-type rlReservation interface {
-	Ok() bool
-	Done(bool)
-}
-
-type rateLimiter interface {
-	AllowIP(net.IP) rlReservation
-	Allow(*authpb.Request) rlReservation
-}
-
 // Keep track of the authenticator ID that last succeeded (i.e. more
 // strict), for further handlers to use.
 type authenticatorIDCtxKeyType int
diff --git a/authn/auth_password.go b/authn/auth_password.go
index c6c3001542f1152e541908fd096682ca60bf4434..855411a9c16f76aeee5f4e1e37f36dc25937ed05 100644
--- a/authn/auth_password.go
+++ b/authn/auth_password.go
@@ -5,8 +5,8 @@ import (
 	"log"
 
 	authpb "git.autistici.org/smol/idp/authn/proto"
-	"git.autistici.org/smol/idp/userdb"
 	pb "git.autistici.org/smol/idp/proto"
+	"git.autistici.org/smol/idp/userdb"
 )
 
 // Authenticator for the primary (main) password. 2FA is usually
diff --git a/authn/auth_recovery.go b/authn/auth_recovery.go
index 6396a03833d7a7839a6c435620fbeca21b313102..2de72ebd543e374c6e66e96929f309cd3776e358 100644
--- a/authn/auth_recovery.go
+++ b/authn/auth_recovery.go
@@ -6,8 +6,8 @@ import (
 	"time"
 
 	authpb "git.autistici.org/smol/idp/authn/proto"
-	"git.autistici.org/smol/idp/userdb"
 	pb "git.autistici.org/smol/idp/proto"
+	"git.autistici.org/smol/idp/userdb"
 )
 
 type recoveryAuthHandler struct {
diff --git a/cmd/smol-idp/account.go b/cmd/smol-idp/account.go
index 99db0be388c1464286bda91b626e5850ebbb15df..97f6199c47fc90d812b86a99cd4206598ddbacd6 100644
--- a/cmd/smol-idp/account.go
+++ b/cmd/smol-idp/account.go
@@ -12,8 +12,8 @@ import (
 	apb "git.autistici.org/smol/idp/audit/proto"
 	authpb "git.autistici.org/smol/idp/authn/proto"
 	"git.autistici.org/smol/idp/internal/netutil"
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/login"
+	"git.autistici.org/smol/idp/userdb"
 	"github.com/google/subcommands"
 )
 
diff --git a/cmd/smol-idp/bundle.go b/cmd/smol-idp/bundle.go
index 80e11fc0d33f4e4bc07ce8ae9c818a5a12a87900..df41efd33eb214a876da21ddf51aaa4089405da2 100644
--- a/cmd/smol-idp/bundle.go
+++ b/cmd/smol-idp/bundle.go
@@ -16,7 +16,6 @@ import (
 	"git.autistici.org/smol/idp/authn"
 	authpb "git.autistici.org/smol/idp/authn/proto"
 	"git.autistici.org/smol/idp/internal/netutil"
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/internal/web"
 	"git.autistici.org/smol/idp/internal/yamlschema"
 	"git.autistici.org/smol/idp/login"
@@ -24,6 +23,7 @@ import (
 	"git.autistici.org/smol/idp/oidc"
 	"git.autistici.org/smol/idp/oidc/storage"
 	"git.autistici.org/smol/idp/ui"
+	"git.autistici.org/smol/idp/userdb"
 	"github.com/google/subcommands"
 	"github.com/gorilla/handlers"
 )
diff --git a/cmd/smol-idp/common.go b/cmd/smol-idp/common.go
index 02d54acc36fa799345d3a1022743a91ae5698df0..b397b389f481f19c2639d38f9702408947c89513 100644
--- a/cmd/smol-idp/common.go
+++ b/cmd/smol-idp/common.go
@@ -27,7 +27,7 @@ const envFlagPrefix = "IDP_"
 // Read flag defaults from the environment.
 func defaultsFromEnv(ff *flag.FlagSet) {
 	ff.VisitAll(func(f *flag.Flag) {
-		envName := envFlagPrefix + strings.ToUpper(strings.Replace(f.Name, "-", "_", -1))
+		envName := envFlagPrefix + strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))
 		if s := os.Getenv(envName); s != "" {
 			if err := f.Value.Set(s); err != nil {
 				log.Printf("error setting flag %s from env variable %s: %v", f.Name, envName, err)
diff --git a/cmd/smol-idp/idp.go b/cmd/smol-idp/idp.go
index 459f6a17116b378b010f2b24547cf64e9669ebfb..15fc371fe3b132c4a8c0551fd90ffe4ff439e296 100644
--- a/cmd/smol-idp/idp.go
+++ b/cmd/smol-idp/idp.go
@@ -14,7 +14,6 @@ import (
 	apb "git.autistici.org/smol/idp/audit/proto"
 	authpb "git.autistici.org/smol/idp/authn/proto"
 	"git.autistici.org/smol/idp/internal/netutil"
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/internal/web"
 	"git.autistici.org/smol/idp/internal/yamlschema"
 	"git.autistici.org/smol/idp/login"
@@ -22,6 +21,7 @@ import (
 	"git.autistici.org/smol/idp/oidc"
 	"git.autistici.org/smol/idp/oidc/storage"
 	"git.autistici.org/smol/idp/ui"
+	"git.autistici.org/smol/idp/userdb"
 	"github.com/google/subcommands"
 	"github.com/gorilla/handlers"
 )
diff --git a/cmd/smol-idp/main.go b/cmd/smol-idp/main.go
index 0c4d17af6e8db915d860bb1e9581749b919509ac..ff4f7961225b06afb5d1d7c88974879fd57ac068 100644
--- a/cmd/smol-idp/main.go
+++ b/cmd/smol-idp/main.go
@@ -26,7 +26,6 @@ func explain(w io.Writer, cmd subcommands.Command) {
 func init() {
 	subcommands.Register(subcommands.HelpCommand(), "help")
 	subcommands.Register(subcommands.CommandsCommand(), "help")
-	//subcommands.Register(subcommands.FlagsCommand(), "help")
 
 	subcommands.DefaultCommander.ExplainCommand = explain
 }
diff --git a/internal/memcache/memcache.go b/internal/memcache/memcache.go
index c1ae86002ef919d8987c4b7ad2647a21ebb8877c..1a9c24063c4f7f7a7db2af5271d492c5abd59ed4 100644
--- a/internal/memcache/memcache.go
+++ b/internal/memcache/memcache.go
@@ -139,7 +139,7 @@ func (c *Client) getConn(ctx context.Context) (*conn, bool, error) {
 func (c *Client) failConn(conn *conn) {
 	c.mx.Lock()
 	if c.cur == conn {
-		conn.Close()
+		_ = conn.Close()
 		c.cur = nil
 	}
 	c.mx.Unlock()
diff --git a/internal/memcache/replication.go b/internal/memcache/replication.go
index 26472a8348ff3827846b0ef742fc9e1816a5b0f5..3d753798c2a3e6cf48bcd5e8d6fc1cf7743ce59a 100644
--- a/internal/memcache/replication.go
+++ b/internal/memcache/replication.go
@@ -55,7 +55,7 @@ func (c *ReplicatedClient) getServers() []*Client {
 	return c.servers.Load().([]*Client)
 }
 
-func (c *ReplicatedClient) pickServers(key []byte, n int) []*Client {
+func (c *ReplicatedClient) pickServers(_ []byte, n int) []*Client {
 	servers := c.getServers()
 	if len(servers) > n {
 		servers = servers[:n]
diff --git a/internal/netutil/grpc_client.go b/internal/netutil/grpc_client.go
index a4f2df165135c4d763917c168b28fe86b56bf363..7f5da95434ebb6c5e3fcc75480c2f0c07c93d55b 100644
--- a/internal/netutil/grpc_client.go
+++ b/internal/netutil/grpc_client.go
@@ -33,7 +33,7 @@ func newConnCache(opts []grpc.DialOption) *grpcConnCache {
 
 func (c *grpcConnCache) Close() error {
 	for _, conn := range c.conns {
-		conn.Close()
+		_ = conn.Close()
 	}
 	return nil
 }
diff --git a/internal/testutil/fake_auth.go b/internal/testutil/fake_auth.go
index 9de132405c858189f857e0b379144ee0c8dfc056..27d0ffa28edecc73a983ce722558b7f71acca2ac 100644
--- a/internal/testutil/fake_auth.go
+++ b/internal/testutil/fake_auth.go
@@ -13,7 +13,7 @@ import (
 var (
 	fakeWebauthnSession          = "webauthn_session"
 	fakeWebauthnEncodedAssertion = "{\"Challenge\":\"webauthn_challenge\"}"
-	testCredentialOrigin         = "https://webauthn.io"
+	testOrigin                   = "https://webauthn.io"
 )
 
 func validWebAuthnRequest(req *authpb.Request) bool {
@@ -28,7 +28,7 @@ func validWebAuthnRequest(req *authpb.Request) bool {
 	if err != nil {
 		return false
 	}
-	if a.Response.CollectedClientData.Origin != testCredentialOrigin {
+	if a.Response.CollectedClientData.Origin != testOrigin {
 		log.Printf("bad webauthn assertion: '%s'", req.WebauthnEncodedAssertion)
 		return false
 	}
@@ -38,7 +38,7 @@ func validWebAuthnRequest(req *authpb.Request) bool {
 type FakeAuthClient struct{}
 
 func (c *FakeAuthClient) Authenticate(ctx context.Context, req *authpb.Request, _ ...grpc.CallOption) (*authpb.Response, error) {
-	p := string(req.Password)
+	p := req.Password
 	info := &pb.UserInfo{
 		Name:   "test",
 		Email:  "test@example.com",
diff --git a/internal/testutil/userdb.go b/internal/testutil/userdb.go
index 67c383cfb1559c26ed24eb6f1fe40b718a06180d..74de61e2012703808e839dd3e17c806a1f975f82 100644
--- a/internal/testutil/userdb.go
+++ b/internal/testutil/userdb.go
@@ -69,7 +69,7 @@ func CreateTestDB(fixtures, dir string) (userdb.Backend, func(), error) {
 	return b, func() {
 		b.Close()
 		if dir == "" {
-			os.RemoveAll(dbdir)
+			_ = os.RemoveAll(dbdir)
 		}
 	}, nil
 }
diff --git a/internal/yamlschema/schema.go b/internal/yamlschema/schema.go
index 45133baf9c14374f29da0bd1705edd894276994c..0766a56da8f2b4b5dd4bb889e17d9da6dbf40cb8 100644
--- a/internal/yamlschema/schema.go
+++ b/internal/yamlschema/schema.go
@@ -20,7 +20,7 @@ import (
 // documentation.
 func DumpSchema(w io.Writer, obj any, prefix string) {
 	bufw := bufio.NewWriter(w)
-	defer bufw.Flush()
+	defer bufw.Flush() //nolint:errcheck
 
 	fmt.Fprintf(bufw, "\n")
 
diff --git a/login/login_test.go b/login/login_test.go
index 116ee0ff906f64081f0d60f98f28d1f027f6914f..85cf5e9d7d5a7ac52419a9b1cb4db2ae08d87617 100644
--- a/login/login_test.go
+++ b/login/login_test.go
@@ -80,7 +80,7 @@ func newClient(baseURI string) *httpClient {
 }
 
 func (c *httpClient) withRequestParams(uri string) string {
-	return uri + "?_rp=" + strings.Replace(c.requestParams, "=", "%3D", -1)
+	return uri + "?_rp=" + strings.ReplaceAll(c.requestParams, "=", "%3D")
 }
 
 func (c *httpClient) formData(values map[string]string) io.Reader {
diff --git a/mgmt/app.go b/mgmt/app.go
index 5b22236be88ed9cb5f6a3cbe6b014c0e1855eb02..0fa9f317a29f2ccdcac09ce85e2b90c8b395b986 100644
--- a/mgmt/app.go
+++ b/mgmt/app.go
@@ -10,10 +10,10 @@ import (
 	"git.autistici.org/smol/idp"
 	authpb "git.autistici.org/smol/idp/authn/proto"
 	"git.autistici.org/smol/idp/internal/cryptutil"
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/internal/web"
 	"git.autistici.org/smol/idp/internal/web/forms"
 	"git.autistici.org/smol/idp/login"
+	"git.autistici.org/smol/idp/userdb"
 	"google.golang.org/grpc"
 )
 
diff --git a/mgmt/app_test.go b/mgmt/app_test.go
index af2b367283dfae45b513e5cf876727d165801ec6..b2f190916f43f3972dfd1f1adc0e82bbd4fc7d6e 100644
--- a/mgmt/app_test.go
+++ b/mgmt/app_test.go
@@ -7,11 +7,11 @@ import (
 
 	"git.autistici.org/smol/idp"
 	"git.autistici.org/smol/idp/internal/testutil"
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/internal/web"
 	"git.autistici.org/smol/idp/login"
 	lpb "git.autistici.org/smol/idp/login/proto"
 	"git.autistici.org/smol/idp/ui"
+	"git.autistici.org/smol/idp/userdb"
 	"github.com/gorilla/securecookie"
 )
 
diff --git a/mgmt/asp.go b/mgmt/asp.go
index 11df36c70e0fd47a78a4f1f43d1fa6b51126c29f..caafb4fd46cc136258dbd628b164d77a4c23e202 100644
--- a/mgmt/asp.go
+++ b/mgmt/asp.go
@@ -4,9 +4,9 @@ import (
 	"net/http"
 
 	"git.autistici.org/smol/idp/internal/random"
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/internal/web"
 	"git.autistici.org/smol/idp/internal/web/forms"
+	"git.autistici.org/smol/idp/userdb"
 )
 
 type listASPAction struct{}
diff --git a/mgmt/change_password.go b/mgmt/change_password.go
index 93c81aef57d3586479167b4ffc26efef8a3b1af5..465b0f2956b98af175346b4a0928b622f05b87f7 100644
--- a/mgmt/change_password.go
+++ b/mgmt/change_password.go
@@ -3,9 +3,9 @@ package mgmt
 import (
 	"net/http"
 
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/internal/web"
 	"git.autistici.org/smol/idp/internal/web/forms"
+	"git.autistici.org/smol/idp/userdb"
 )
 
 const changePasswordWorkflow = "change-password"
diff --git a/mgmt/messages.go b/mgmt/messages.go
index a40ac152a6d3cb726c01f4f543a5396e4b468f5f..e6a4fd4d13004e5fe58f34ebb0e014de0ab9a6b3 100644
--- a/mgmt/messages.go
+++ b/mgmt/messages.go
@@ -6,8 +6,8 @@ import (
 	"html/template"
 	"net/http"
 
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/internal/web"
+	"git.autistici.org/smol/idp/userdb"
 )
 
 type messageAction struct {
@@ -31,7 +31,9 @@ func (a *messageAction) ServeAction(req *http.Request, user *userdb.User, tpl *w
 		return newErrorResponse(http.StatusInternalServerError)
 	}
 	return htmlResponse(tpl.WithData(map[string]any{
-		"Content": template.HTML(buf.String()),
+		// Message contents are under our control and we trust
+		// them to be valid HTML.
+		"Content": template.HTML(buf.String()), //nolint:gosec
 	}), req, "account_message.html")
 }
 
diff --git a/mgmt/recovery.go b/mgmt/recovery.go
index a644ab6d30443b641248ca485054c24b028acfa9..063795f7fed5ee97980b08b595b990eeb9f0dba2 100644
--- a/mgmt/recovery.go
+++ b/mgmt/recovery.go
@@ -5,10 +5,10 @@ import (
 	"time"
 
 	"git.autistici.org/smol/idp/internal/random"
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/internal/web"
 	"git.autistici.org/smol/idp/internal/web/forms"
 	pb "git.autistici.org/smol/idp/proto"
+	"git.autistici.org/smol/idp/userdb"
 )
 
 const resetRecoveryWorkflow = "set-recovery"
diff --git a/mgmt/util.go b/mgmt/util.go
index 8ee2e9007880d843c21fea3dc9678e6b8f678910..c8efbd298ed8025b91b9ebb1a71abc95a524123b 100644
--- a/mgmt/util.go
+++ b/mgmt/util.go
@@ -4,11 +4,11 @@ import (
 	"net/http"
 
 	"git.autistici.org/smol/idp/internal/sessionstore"
-	"git.autistici.org/smol/idp/userdb"
 	"git.autistici.org/smol/idp/internal/web"
 	"git.autistici.org/smol/idp/internal/web/forms"
 	"git.autistici.org/smol/idp/login"
 	pb "git.autistici.org/smol/idp/proto"
+	"git.autistici.org/smol/idp/userdb"
 )
 
 // Minimal web framework, tailored for this specific kind of app.
diff --git a/oidc/op.go b/oidc/op.go
index eb27bbd602949623f99aa137ef6b2f94dcfd6c54..21f32d19a100d44cbf121d367375bf94a05ff046 100644
--- a/oidc/op.go
+++ b/oidc/op.go
@@ -135,7 +135,7 @@ func NewOP(storage op.Storage, issuer string, key [32]byte, insecure bool) (op.O
 	}
 
 	if insecure {
-		//we must explicitly allow the use of the http issuer
+		// We must explicitly allow the use of the http issuer.
 		opts = append(opts, op.WithAllowInsecure())
 	}
 
diff --git a/oidc/storage/oidc.go b/oidc/storage/oidc.go
index 3feda7430f4fdd097e21acadfb43b514f0b59242..5f7b72f3ba89d50558ca2f02b0685f15eb2964c3 100644
--- a/oidc/storage/oidc.go
+++ b/oidc/storage/oidc.go
@@ -11,13 +11,14 @@ import (
 
 const (
 	// CustomScope is an example for how to use custom scopes in this library
-	//(in this scenario, when requested, it will return a custom claim)
+	// (in this scenario, when requested, it will return a custom claim).
 	CustomScope = "custom_scope"
 
-	// CustomClaim is an example for how to return custom claims with this library
+	// CustomClaim is an example for how to return custom claims with this library.
 	CustomClaim = "custom_claim"
 
-	// CustomScopeImpersonatePrefix is an example scope prefix for passing user id to impersonate using token exchage
+	// CustomScopeImpersonatePrefix is an example scope prefix for
+	// passing user id to impersonate using token exchange.
 	CustomScopeImpersonatePrefix = "custom_scope:impersonate:"
 
 	GroupsScope = "groups"
diff --git a/oidc/storage/op_storage.go b/oidc/storage/op_storage.go
index 992b4d897fdd17a6320568536de9920e189f3f36..6c659ca9a49a695ad59d4e05c52222ca3f18432b 100644
--- a/oidc/storage/op_storage.go
+++ b/oidc/storage/op_storage.go
@@ -60,23 +60,25 @@ func (s *opStorage) SetIntrospectionFromToken(ctx context.Context, introspection
 	if err != nil {
 		return fmt.Errorf("token is invalid or has expired")
 	}
-	// check if the client is part of the requested audience
+
+	// Check if the client is part of the requested audience.
 	for _, aud := range token.Audience {
 		if aud == clientID {
-			// the introspection response only has to return a boolean (active) if the token is active
-			// this will automatically be done by the library if you don't return an error
-			// you can also return further information about the user / associated token
-			// e.g. the userinfo (equivalent to userinfo endpoint)
-
+			// The introspection response only has to return a
+			// boolean (active) if the token is active this will
+			// automatically be done by the library if you don't
+			// return an error you can also return further
+			// information about the user / associated token e.g.
+			// the userinfo (equivalent to userinfo endpoint).
 			userInfo := new(oidc.UserInfo)
 			err := s.setUserinfo(ctx, userInfo, subject, clientID, token.Scopes)
 			if err != nil {
 				return err
 			}
 			introspection.SetUserInfo(userInfo)
-			//...and also the requested scopes...
+			// ...and also the requested scopes...
 			introspection.Scope = token.Scopes
-			//...and the client the token was issued to
+			// ...and the client the token was issued to.
 			introspection.ClientID = token.ClientID
 			return nil
 		}
@@ -122,7 +124,7 @@ func (s *opStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
 	return s.getPrivateClaimsFromScopes(ctx, userID, clientID, scopes)
 }
 
-func (s *opStorage) getPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (claims map[string]any, err error) {
+func (s *opStorage) getPrivateClaimsFromScopes(_ context.Context, userID, clientID string, scopes []string) (claims map[string]any, err error) {
 	for _, scope := range scopes {
 		switch scope {
 		case CustomScope:
@@ -210,7 +212,7 @@ func (s *tokenExchangeStorage) SetUserinfoFromTokenExchangeRequest(ctx context.C
 	return nil
 }
 
-func (s *tokenExchangeStorage) getTokenExchangeClaims(ctx context.Context, request op.TokenExchangeRequest) (claims map[string]any) {
+func (s *tokenExchangeStorage) getTokenExchangeClaims(_ context.Context, request op.TokenExchangeRequest) (claims map[string]any) {
 	for _, scope := range request.GetScopes() {
 		switch {
 		case strings.HasPrefix(scope, CustomScopeImpersonatePrefix) && request.GetExchangeActor() == "":
diff --git a/userdb/backend/all/all_test.go b/userdb/backend/all/all_test.go
index 66d156ffcf9f91b0e36d1c38ab637462e38b596f..a5573c559e9a1fceb56b165645ee120729c71c0c 100644
--- a/userdb/backend/all/all_test.go
+++ b/userdb/backend/all/all_test.go
@@ -100,10 +100,11 @@ func setPassword(user *userdb.User, pw string) error {
 	return nil
 }
 
-func runTestNotFound(t *testing.T, db userdb.Backend, username string) {
+func runTestNotFound(t *testing.T, db userdb.Backend) {
 	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
 	defer cancel()
 
+	username := "nosuchuser"
 	user, err := db.GetUserByName(ctx, username)
 	if !errors.Is(err, userdb.ErrNotFound) {
 		t.Fatalf("GetUserByName(%s): expected ErrNotFound, got err=%v, user=%+v", username, err, user)
@@ -151,7 +152,7 @@ func TestBackend_SQL(t *testing.T) {
 	defer cleanup()
 
 	runTest(t, db, "test", false)
-	runTestNotFound(t, db, "nosuchuser")
+	runTestNotFound(t, db)
 }
 
 func TestBackend_Static(t *testing.T) {
@@ -162,7 +163,7 @@ func TestBackend_Static(t *testing.T) {
 	defer db.Close()
 
 	runTest(t, db, "test", true)
-	runTestNotFound(t, db, "nosuchuser")
+	runTestNotFound(t, db)
 }
 
 func TestBackend_Chain(t *testing.T) {
@@ -174,7 +175,7 @@ func TestBackend_Chain(t *testing.T) {
 
 	runTest(t, db, "test", true) // static backend comes first
 	runTest(t, db, "test-sqlonly", false)
-	runTestNotFound(t, db, "nosuchuser")
+	runTestNotFound(t, db)
 }
 
 func TestBackend_RPC(t *testing.T) {
@@ -191,5 +192,5 @@ func TestBackend_RPC(t *testing.T) {
 	defer cleanup2()
 
 	runTest(t, db, "test", false)
-	runTestNotFound(t, db, "nosuchuser")
+	runTestNotFound(t, db)
 }
diff --git a/userdb/backend/rpc/rpc.go b/userdb/backend/rpc/rpc.go
index 36ab8617c366f86bce0fd1bc4e4c0ed9bf0fa500..769ddd54be45edb90327e0955a6385dc56aa3112 100644
--- a/userdb/backend/rpc/rpc.go
+++ b/userdb/backend/rpc/rpc.go
@@ -5,8 +5,8 @@ import (
 
 	accpb "git.autistici.org/smol/idp/account/proto"
 	"git.autistici.org/smol/idp/internal/netutil"
-	"git.autistici.org/smol/idp/userdb"
 	pb "git.autistici.org/smol/idp/proto"
+	"git.autistici.org/smol/idp/userdb"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
@@ -59,8 +59,7 @@ func (b *rpcBackend) GetUserByName(ctx context.Context, username string) (*userd
 func (b *rpcBackend) getUser(ctx context.Context, req *accpb.GetUserRequest) (*userdb.User, error) {
 	u, err := b.client.GetUser(ctx, req)
 	if err != nil {
-		switch status.Code(err) {
-		case codes.NotFound:
+		if status.Code(err) == codes.NotFound {
 			err = userdb.ErrNotFound
 		}
 		return nil, err
diff --git a/userdb/backend/sql/sql.go b/userdb/backend/sql/sql.go
index ec833b31d4e35b957879adad9d86c91196c65682..68deaf2d61427d3bdc54ef95aedebeb3c21cc5ee 100644
--- a/userdb/backend/sql/sql.go
+++ b/userdb/backend/sql/sql.go
@@ -403,6 +403,8 @@ func newSQLBackendFromParams(sparams *sqlParams) (userdb.Backend, error) {
 	switch sparams.Driver {
 	case "sqlite3":
 		err = setupSQLite(db)
+	default:
+		err = errors.New("unsupported sql driver")
 	}
 	if err != nil {
 		return nil, err