diff --git a/backend/composite_values.go b/backend/composite_values.go
index c6c57006c0bd5714e8a7f1da6f09c9c331fbe7f5..2d5f84b00028a985b2f02a5ca22ad89bc461602f 100644
--- a/backend/composite_values.go
+++ b/backend/composite_values.go
@@ -5,13 +5,13 @@ import (
 	"fmt"
 	"strings"
 
-	"git.autistici.org/ai3/accountserver"
+	as "git.autistici.org/ai3/accountserver"
 )
 
 // Extend the AppSpecificPasswordInfo type, which only contains public
 // information, with the encrypted password.
 type appSpecificPassword struct {
-	accountserver.AppSpecificPasswordInfo
+	as.AppSpecificPasswordInfo
 	Password string
 }
 
@@ -23,7 +23,7 @@ func (p *appSpecificPassword) Encode() string {
 	}, ":")
 }
 
-func newAppSpecificPassword(info accountserver.AppSpecificPasswordInfo, pw string) *appSpecificPassword {
+func newAppSpecificPassword(info as.AppSpecificPasswordInfo, pw string) *appSpecificPassword {
 	return &appSpecificPassword{
 		AppSpecificPasswordInfo: info,
 		Password:                pw,
@@ -35,7 +35,7 @@ func parseAppSpecificPassword(asp string) (*appSpecificPassword, error) {
 	if len(parts) != 3 {
 		return nil, errors.New("badly encoded app-specific password")
 	}
-	return newAppSpecificPassword(accountserver.AppSpecificPasswordInfo{
+	return newAppSpecificPassword(as.AppSpecificPasswordInfo{
 		Service: parts[0],
 		Comment: parts[2],
 	}, parts[1]), nil
@@ -59,22 +59,22 @@ func encodeAppSpecificPasswords(asps []*appSpecificPassword) []string {
 	return out
 }
 
-func getASPInfo(asps []*appSpecificPassword) []*accountserver.AppSpecificPasswordInfo {
-	var out []*accountserver.AppSpecificPasswordInfo
+func getASPInfo(asps []*appSpecificPassword) []*as.AppSpecificPasswordInfo {
+	var out []*as.AppSpecificPasswordInfo
 	for _, asp := range asps {
 		out = append(out, &asp.AppSpecificPasswordInfo)
 	}
 	return out
 }
 
-func decodeUserEncryptionKeys(values []string) []*accountserver.UserEncryptionKey {
-	var out []*accountserver.UserEncryptionKey
+func decodeUserEncryptionKeys(values []string) []*as.UserEncryptionKey {
+	var out []*as.UserEncryptionKey
 	for _, value := range values {
 		idx := strings.IndexByte(value, ':')
 		if idx < 0 {
 			continue
 		}
-		out = append(out, &accountserver.UserEncryptionKey{
+		out = append(out, &as.UserEncryptionKey{
 			ID:  value[:idx],
 			Key: []byte(value[idx+1:]),
 		})
@@ -82,7 +82,7 @@ func decodeUserEncryptionKeys(values []string) []*accountserver.UserEncryptionKe
 	return out
 }
 
-func encodeUserEncryptionKeys(keys []*accountserver.UserEncryptionKey) []string {
+func encodeUserEncryptionKeys(keys []*as.UserEncryptionKey) []string {
 	var out []string
 	for _, key := range keys {
 		out = append(out, fmt.Sprintf("%s:%s", key.ID, string(key.Key)))
diff --git a/backend/model.go b/backend/model.go
index 31a07bce8765709f36163bd3ef1d5e4f790b3cae..8946b4e5a2c048bcfd02755e01bbf3f1a00d320a 100644
--- a/backend/model.go
+++ b/backend/model.go
@@ -11,7 +11,7 @@ import (
 	"github.com/tstranex/u2f"
 	"gopkg.in/ldap.v2"
 
-	"git.autistici.org/ai3/accountserver"
+	as "git.autistici.org/ai3/accountserver"
 )
 
 const (
@@ -59,7 +59,7 @@ type backendTX struct {
 
 const ldapPoolSize = 20
 
-func (b *backend) NewTransaction() (accountserver.TX, error) {
+func (b *backend) NewTransaction() (as.TX, error) {
 	return &backendTX{
 		ldapTX:  newLDAPTX(b.conn),
 		backend: b,
@@ -68,7 +68,7 @@ func (b *backend) NewTransaction() (accountserver.TX, error) {
 
 // NewLDAPBackend initializes an LDAPBackend object with the given LDAP
 // connection pool.
-func NewLDAPBackend(uri, bindDN, bindPw, base string) (accountserver.Backend, error) {
+func NewLDAPBackend(uri, bindDN, bindPw, base string) (as.Backend, error) {
 	pool, err := ldaputil.NewConnectionPool(uri, bindDN, bindPw, ldapPoolSize)
 	if err != nil {
 		return nil, err
@@ -78,12 +78,12 @@ func NewLDAPBackend(uri, bindDN, bindPw, base string) (accountserver.Backend, er
 
 func newLDAPBackendWithConn(conn ldapConn, base string) (*backend, error) {
 	rsrc := newResourceRegistry()
-	rsrc.register(accountserver.ResourceTypeEmail, &emailResourceHandler{baseDN: base})
-	rsrc.register(accountserver.ResourceTypeMailingList, &mailingListResourceHandler{baseDN: base})
-	rsrc.register(accountserver.ResourceTypeDAV, &webdavResourceHandler{baseDN: base})
-	rsrc.register(accountserver.ResourceTypeWebsite, &websiteResourceHandler{baseDN: base})
-	rsrc.register(accountserver.ResourceTypeDomain, &domainResourceHandler{baseDN: base})
-	rsrc.register(accountserver.ResourceTypeDatabase, &databaseResourceHandler{baseDN: base})
+	rsrc.register(as.ResourceTypeEmail, &emailResourceHandler{baseDN: base})
+	rsrc.register(as.ResourceTypeMailingList, &mailingListResourceHandler{baseDN: base})
+	rsrc.register(as.ResourceTypeDAV, &webdavResourceHandler{baseDN: base})
+	rsrc.register(as.ResourceTypeWebsite, &websiteResourceHandler{baseDN: base})
+	rsrc.register(as.ResourceTypeDomain, &domainResourceHandler{baseDN: base})
+	rsrc.register(as.ResourceTypeDatabase, &databaseResourceHandler{baseDN: base})
 
 	return &backend{
 		conn:   conn,
@@ -112,7 +112,7 @@ func newLDAPBackendWithConn(conn ldapConn, base string) (*backend, error) {
 	}, nil
 }
 
-func newUser(entry *ldap.Entry) (*accountserver.User, error) {
+func newUser(entry *ldap.Entry) (*as.User, error) {
 	// Note that some user-level attributes related to
 	// authentication are stored on the uid= object, while others
 	// are on the email= object. We set the latter in the GetUser
@@ -121,8 +121,8 @@ func newUser(entry *ldap.Entry) (*accountserver.User, error) {
 	// The case of password recovery attributes is more complex:
 	// the current schema has those on email=, but we'd like to
 	// move them to uid=, so we currently have to support both.
-	uidNumber, _ := strconv.Atoi(entry.GetAttributeValue(uidNumberLDAPAttr))
-	user := &accountserver.User{
+	uidNumber, _ := strconv.Atoi(entry.GetAttributeValue(uidNumberLDAPAttr)) // nolint
+	user := &as.User{
 		Name:                 entry.GetAttributeValue("uid"),
 		Lang:                 entry.GetAttributeValue(preferredLanguageLDAPAttr),
 		UID:                  uidNumber,
@@ -140,7 +140,7 @@ func newUser(entry *ldap.Entry) (*accountserver.User, error) {
 	return user, nil
 }
 
-func userToLDAP(user *accountserver.User) (attrs []ldap.PartialAttribute) {
+func userToLDAP(user *as.User) (attrs []ldap.PartialAttribute) {
 	// Most attributes are read-only and have specialized methods to set them.
 	attrs = append(attrs, []ldap.PartialAttribute{
 		{Type: "objectClass", Vals: []string{"top", "person", "posixAccount", "shadowAccount", "organizationalPerson", "inetOrgPerson", "totpAccount"}},
@@ -161,21 +161,22 @@ func userToLDAP(user *accountserver.User) (attrs []ldap.PartialAttribute) {
 	return
 }
 
-func decodeU2FRegistration(enc string) (*u2f.Registration, error) {
+func decodeU2FRegistration(enc string) (*as.U2FRegistration, error) {
 	var reg u2f.Registration
 	if err := reg.UnmarshalBinary([]byte(enc)); err != nil {
 		return nil, err
 	}
-	return &reg, nil
+	return &as.U2FRegistration{Registration: &reg}, nil
 }
 
-func encodeU2FRegistration(r *u2f.Registration) string {
-	b, _ := r.MarshalBinary()
+func encodeU2FRegistration(r *as.U2FRegistration) string {
+	// MarshalBinary can't fail, ignore error.
+	b, _ := r.MarshalBinary() // nolint
 	return string(b)
 }
 
-func decodeU2FRegistrations(encRegs []string) []*u2f.Registration {
-	var out []*u2f.Registration
+func decodeU2FRegistrations(encRegs []string) []*as.U2FRegistration {
+	var out []*as.U2FRegistration
 	for _, enc := range encRegs {
 		if r, err := decodeU2FRegistration(enc); err == nil {
 			out = append(out, r)
@@ -184,7 +185,7 @@ func decodeU2FRegistrations(encRegs []string) []*u2f.Registration {
 	return out
 }
 
-func encodeU2FRegistrations(regs []*u2f.Registration) []string {
+func encodeU2FRegistrations(regs []*as.U2FRegistration) []string {
 	var out []string
 	for _, r := range regs {
 		out = append(out, encodeU2FRegistration(r))
@@ -192,12 +193,12 @@ func encodeU2FRegistrations(regs []*u2f.Registration) []string {
 	return out
 }
 
-func (tx *backendTX) getUserDN(user *accountserver.User) string {
+func (tx *backendTX) getUserDN(user *as.User) string {
 	return joinDN("uid="+user.Name, "ou=People", tx.backend.baseDN)
 }
 
 // CreateUser creates a new user.
-func (tx *backendTX) CreateUser(ctx context.Context, user *accountserver.User) error {
+func (tx *backendTX) CreateUser(ctx context.Context, user *as.User) error {
 	dn := tx.getUserDN(user)
 
 	tx.create(dn)
@@ -216,7 +217,7 @@ func (tx *backendTX) CreateUser(ctx context.Context, user *accountserver.User) e
 }
 
 // UpdateUser updates values for the user only (not the resources).
-func (tx *backendTX) UpdateUser(ctx context.Context, user *accountserver.User) error {
+func (tx *backendTX) UpdateUser(ctx context.Context, user *as.User) error {
 	dn := tx.getUserDN(user)
 	for _, attr := range userToLDAP(user) {
 		tx.setAttr(dn, attr.Type, attr.Vals...)
@@ -225,7 +226,7 @@ func (tx *backendTX) UpdateUser(ctx context.Context, user *accountserver.User) e
 }
 
 // GetUser returns a user.
-func (tx *backendTX) GetUser(ctx context.Context, username string) (*accountserver.User, error) {
+func (tx *backendTX) GetUser(ctx context.Context, username string) (*as.User, error) {
 	// First of all, find the main user object, and just that one.
 	vars := map[string]string{"user": username}
 	result, err := tx.search(ctx, tx.backend.userQuery.query(vars))
@@ -276,17 +277,20 @@ func (tx *backendTX) GetUser(ctx context.Context, username string) (*accountserv
 	return user, nil
 }
 
-func (tx *backendTX) SetUserPassword(ctx context.Context, user *accountserver.User, encryptedPassword string) error {
+func (tx *backendTX) SetUserPassword(ctx context.Context, user *as.User, encryptedPassword string) (err error) {
 	dn := tx.getUserDN(user)
 	tx.setAttr(dn, passwordLDAPAttr, encryptedPassword)
-	for _, r := range user.GetResourcesByType(accountserver.ResourceTypeEmail) {
-		dn, _ = tx.backend.resources.GetDN(r.ID)
+	for _, r := range user.GetResourcesByType(as.ResourceTypeEmail) {
+		dn, err = tx.backend.resources.GetDN(r.ID)
+		if err != nil {
+			return
+		}
 		tx.setAttr(dn, passwordLDAPAttr, encryptedPassword)
 	}
-	return nil
+	return
 }
 
-func (tx *backendTX) GetUserEncryptedPassword(ctx context.Context, user *accountserver.User) string {
+func (tx *backendTX) GetUserEncryptedPassword(ctx context.Context, user *as.User) string {
 	values := tx.readAttributeValues(ctx, tx.getUserDN(user), passwordLDAPAttr)
 	if len(values) > 0 {
 		// Remove legacy LDAP encryption prefix.
@@ -295,7 +299,7 @@ func (tx *backendTX) GetUserEncryptedPassword(ctx context.Context, user *account
 	return ""
 }
 
-func (tx *backendTX) GetUserRecoveryEncryptedPassword(ctx context.Context, user *accountserver.User) string {
+func (tx *backendTX) GetUserRecoveryEncryptedPassword(ctx context.Context, user *as.User) string {
 	values := tx.readAttributeValues(ctx, tx.getUserDN(user), recoveryResponseLDAPAttr)
 	if len(values) > 0 {
 		// Remove legacy LDAP encryption prefix.
@@ -304,7 +308,7 @@ func (tx *backendTX) GetUserRecoveryEncryptedPassword(ctx context.Context, user
 	return ""
 }
 
-func (tx *backendTX) SetPasswordRecoveryHint(ctx context.Context, user *accountserver.User, hint, response string) error {
+func (tx *backendTX) SetPasswordRecoveryHint(ctx context.Context, user *as.User, hint, response string) error {
 	// Write the password recovery attributes on the uid= object,
 	// as per the new schema.
 	dn := tx.getUserDN(user)
@@ -313,25 +317,34 @@ func (tx *backendTX) SetPasswordRecoveryHint(ctx context.Context, user *accounts
 	return nil
 }
 
-func (tx *backendTX) GetUserEncryptionKeys(ctx context.Context, user *accountserver.User) ([]*accountserver.UserEncryptionKey, error) {
-	r := user.GetSingleResourceByType(accountserver.ResourceTypeEmail)
-	dn, _ := tx.backend.resources.GetDN(r.ID)
+func (tx *backendTX) GetUserEncryptionKeys(ctx context.Context, user *as.User) ([]*as.UserEncryptionKey, error) {
+	r := user.GetSingleResourceByType(as.ResourceTypeEmail)
+	dn, err := tx.backend.resources.GetDN(r.ID)
+	if err != nil {
+		return nil, err
+	}
 	rawKeys := tx.readAttributeValues(ctx, dn, storagePrivateKeyLDAPAttr)
 	return decodeUserEncryptionKeys(rawKeys), nil
 }
 
-func (tx *backendTX) SetUserEncryptionKeys(ctx context.Context, user *accountserver.User, keys []*accountserver.UserEncryptionKey) error {
+func (tx *backendTX) SetUserEncryptionKeys(ctx context.Context, user *as.User, keys []*as.UserEncryptionKey) error {
 	encKeys := encodeUserEncryptionKeys(keys)
-	for _, r := range user.GetResourcesByType(accountserver.ResourceTypeEmail) {
-		dn, _ := tx.backend.resources.GetDN(r.ID)
+	for _, r := range user.GetResourcesByType(as.ResourceTypeEmail) {
+		dn, err := tx.backend.resources.GetDN(r.ID)
+		if err != nil {
+			return err
+		}
 		tx.setAttr(dn, storagePrivateKeyLDAPAttr, encKeys...)
 	}
 	return nil
 }
 
-func (tx *backendTX) SetUserEncryptionPublicKey(ctx context.Context, user *accountserver.User, pub []byte) error {
-	for _, r := range user.GetResourcesByType(accountserver.ResourceTypeEmail) {
-		dn, _ := tx.backend.resources.GetDN(r.ID)
+func (tx *backendTX) SetUserEncryptionPublicKey(ctx context.Context, user *as.User, pub []byte) error {
+	for _, r := range user.GetResourcesByType(as.ResourceTypeEmail) {
+		dn, err := tx.backend.resources.GetDN(r.ID)
+		if err != nil {
+			return err
+		}
 		tx.setAttr(dn, storagePublicKeyLDAPAttr, string(pub))
 	}
 	return nil
@@ -347,8 +360,11 @@ func excludeASPFromList(asps []*appSpecificPassword, id string) []*appSpecificPa
 	return out
 }
 
-func (tx *backendTX) setASPOnResource(ctx context.Context, r *accountserver.Resource, info *accountserver.AppSpecificPasswordInfo, encryptedPassword string) {
-	dn, _ := tx.backend.resources.GetDN(r.ID)
+func (tx *backendTX) setASPOnResource(ctx context.Context, r *as.Resource, info *as.AppSpecificPasswordInfo, encryptedPassword string) {
+	dn, err := tx.backend.resources.GetDN(r.ID)
+	if err != nil {
+		return
+	}
 
 	// Obtain the full list of ASPs from the backend and replace/append the new one.
 	asps := decodeAppSpecificPasswords(tx.readAttributeValues(ctx, dn, aspLDAPAttr))
@@ -357,40 +373,46 @@ func (tx *backendTX) setASPOnResource(ctx context.Context, r *accountserver.Reso
 	tx.setAttr(dn, aspLDAPAttr, outASPs...)
 }
 
-func (tx *backendTX) SetApplicationSpecificPassword(ctx context.Context, user *accountserver.User, info *accountserver.AppSpecificPasswordInfo, encryptedPassword string) error {
-	for _, r := range user.GetResourcesByType(accountserver.ResourceTypeEmail) {
+func (tx *backendTX) SetApplicationSpecificPassword(ctx context.Context, user *as.User, info *as.AppSpecificPasswordInfo, encryptedPassword string) error {
+	for _, r := range user.GetResourcesByType(as.ResourceTypeEmail) {
 		tx.setASPOnResource(ctx, r, info, encryptedPassword)
 	}
 	return nil
 }
 
-func (tx *backendTX) deleteASPOnResource(ctx context.Context, r *accountserver.Resource, id string) {
-	dn, _ := tx.backend.resources.GetDN(r.ID)
+func (tx *backendTX) deleteASPOnResource(ctx context.Context, r *as.Resource, id string) {
+	dn, err := tx.backend.resources.GetDN(r.ID)
+	if err != nil {
+		return
+	}
 	asps := decodeAppSpecificPasswords(tx.readAttributeValues(ctx, dn, aspLDAPAttr))
 	asps = excludeASPFromList(asps, id)
 	outASPs := encodeAppSpecificPasswords(asps)
 	tx.setAttr(dn, aspLDAPAttr, outASPs...)
 }
 
-func (tx *backendTX) DeleteApplicationSpecificPassword(ctx context.Context, user *accountserver.User, id string) error {
-	for _, r := range user.GetResourcesByType(accountserver.ResourceTypeEmail) {
+func (tx *backendTX) DeleteApplicationSpecificPassword(ctx context.Context, user *as.User, id string) error {
+	for _, r := range user.GetResourcesByType(as.ResourceTypeEmail) {
 		tx.deleteASPOnResource(ctx, r, id)
 	}
 	return nil
 }
 
-func (tx *backendTX) SetUserTOTPSecret(ctx context.Context, user *accountserver.User, secret string) error {
+func (tx *backendTX) SetUserTOTPSecret(ctx context.Context, user *as.User, secret string) error {
 	tx.setAttr(tx.getUserDN(user), totpSecretLDAPAttr, secret)
 	return nil
 }
 
-func (tx *backendTX) DeleteUserTOTPSecret(ctx context.Context, user *accountserver.User) error {
+func (tx *backendTX) DeleteUserTOTPSecret(ctx context.Context, user *as.User) error {
 	tx.setAttr(tx.getUserDN(user), totpSecretLDAPAttr)
 	return nil
 }
 
-func (tx *backendTX) SetResourcePassword(ctx context.Context, r *accountserver.Resource, encryptedPassword string) error {
-	dn, _ := tx.backend.resources.GetDN(r.ID)
+func (tx *backendTX) SetResourcePassword(ctx context.Context, r *as.Resource, encryptedPassword string) error {
+	dn, err := tx.backend.resources.GetDN(r.ID)
+	if err != nil {
+		return err
+	}
 	tx.setAttr(dn, passwordLDAPAttr, encryptedPassword)
 	return nil
 }
@@ -420,7 +442,7 @@ func (tx *backendTX) hasResource(ctx context.Context, resourceType, resourceName
 }
 
 // HasAnyResource returns true if any of the specified resources exists.
-func (tx *backendTX) HasAnyResource(ctx context.Context, resourceIDs []accountserver.FindResourceRequest) (bool, error) {
+func (tx *backendTX) HasAnyResource(ctx context.Context, resourceIDs []as.FindResourceRequest) (bool, error) {
 	for _, req := range resourceIDs {
 		has, err := tx.hasResource(ctx, req.Type, req.Name)
 		if err != nil || has {
@@ -431,7 +453,7 @@ func (tx *backendTX) HasAnyResource(ctx context.Context, resourceIDs []accountse
 }
 
 // GetResource returns a ResourceWrapper, as part of a read-modify-update transaction.
-func (tx *backendTX) GetResource(ctx context.Context, rsrcID accountserver.ResourceID) (*accountserver.Resource, error) {
+func (tx *backendTX) GetResource(ctx context.Context, rsrcID as.ResourceID) (*as.Resource, error) {
 	// From the resource ID we can obtain the DN, and fetch it
 	// straight from LDAP without even doing a real search.
 	dn, err := tx.backend.resources.GetDN(rsrcID)
@@ -465,7 +487,7 @@ func (tx *backendTX) GetResource(ctx context.Context, rsrcID accountserver.Resou
 }
 
 // CreateResource creates a new LDAP-backed resource object.
-func (tx *backendTX) CreateResource(ctx context.Context, r *accountserver.Resource) error {
+func (tx *backendTX) CreateResource(ctx context.Context, r *as.Resource) error {
 	dn, err := tx.backend.resources.GetDN(r.ID)
 	if err != nil {
 		return err
@@ -480,7 +502,7 @@ func (tx *backendTX) CreateResource(ctx context.Context, r *accountserver.Resour
 }
 
 // UpdateResource updates a LDAP-backed resource that was obtained by a previous GetResource call.
-func (tx *backendTX) UpdateResource(ctx context.Context, r *accountserver.Resource) error {
+func (tx *backendTX) UpdateResource(ctx context.Context, r *as.Resource) error {
 	dn, err := tx.backend.resources.GetDN(r.ID)
 	if err != nil {
 		return err
diff --git a/backend/model_test.go b/backend/model_test.go
index 365f6d2d43bdf4594c4a5c42687201c627fe89fc..05a3c1fa6f1de0d6d7659ca7fe50cb11894236d6 100644
--- a/backend/model_test.go
+++ b/backend/model_test.go
@@ -6,7 +6,7 @@ import (
 
 	"github.com/go-test/deep"
 
-	"git.autistici.org/ai3/accountserver"
+	as "git.autistici.org/ai3/accountserver"
 	"git.autistici.org/ai3/accountserver/ldaptest"
 )
 
@@ -17,15 +17,15 @@ const (
 	testUser2    = "due@investici.org"
 )
 
-func startServerAndGetUser(t testing.TB) (func(), accountserver.Backend, *accountserver.User) {
+func startServerAndGetUser(t testing.TB) (func(), as.Backend, *as.User) {
 	return startServerAndGetUserWithName(t, testUser1)
 }
 
-func startServerAndGetUser2(t testing.TB) (func(), accountserver.Backend, *accountserver.User) {
+func startServerAndGetUser2(t testing.TB) (func(), as.Backend, *as.User) {
 	return startServerAndGetUserWithName(t, testUser2)
 }
 
-func startServer(t testing.TB) (func(), accountserver.Backend) {
+func startServer(t testing.TB) (func(), as.Backend) {
 	stop := ldaptest.StartServer(t, &ldaptest.Config{
 		Dir:  "../ldaptest",
 		Port: testLDAPPort,
@@ -45,7 +45,7 @@ func startServer(t testing.TB) (func(), accountserver.Backend) {
 	return stop, b
 }
 
-func startServerAndGetUserWithName(t testing.TB, username string) (func(), accountserver.Backend, *accountserver.User) {
+func startServerAndGetUserWithName(t testing.TB, username string) (func(), as.Backend, *as.User) {
 	stop, b := startServer(t)
 
 	tx, _ := b.NewTransaction()
@@ -86,25 +86,25 @@ func TestModel_GetUser(t *testing.T) {
 	}
 
 	// Test a specific resource (the database).
-	db := user.GetSingleResourceByType(accountserver.ResourceTypeDatabase)
-	expectedDB := &accountserver.Resource{
-		ID: accountserver.NewResourceID(
-			accountserver.ResourceTypeDatabase,
+	db := user.GetSingleResourceByType(as.ResourceTypeDatabase)
+	expectedDB := &as.Resource{
+		ID: as.NewResourceID(
+			as.ResourceTypeDatabase,
 			testUser1,
 			"alias=uno",
 			"unodb",
 		),
-		Type: accountserver.ResourceTypeDatabase,
-		ParentID: accountserver.NewResourceID(
-			accountserver.ResourceTypeWebsite,
+		Type: as.ResourceTypeDatabase,
+		ParentID: as.NewResourceID(
+			as.ResourceTypeWebsite,
 			testUser1,
 			"uno",
 		),
 		Name:          "unodb",
 		Shard:         "host2",
 		OriginalShard: "host2",
-		Status:        accountserver.ResourceStatusActive,
-		Database: &accountserver.Database{
+		Status:        as.ResourceStatusActive,
+		Database: &as.Database{
 			CleartextPassword: "password",
 			DBUser:            "unodb",
 		},
@@ -168,7 +168,7 @@ func TestModel_SetResourceStatus(t *testing.T) {
 	}
 
 	tx, _ := b.NewTransaction()
-	rsrcID := accountserver.NewResourceID(accountserver.ResourceTypeEmail, testUser1, testUser1)
+	rsrcID := as.NewResourceID(as.ResourceTypeEmail, testUser1, testUser1)
 	r, err := tx.GetResource(context.Background(), rsrcID)
 	if err != nil {
 		t.Fatal("GetResource", err)
@@ -177,7 +177,7 @@ func TestModel_SetResourceStatus(t *testing.T) {
 		t.Fatalf("could not find test resource %s", rsrcID)
 	}
 
-	r.Status = accountserver.ResourceStatusInactive
+	r.Status = as.ResourceStatusInactive
 	if err := tx.UpdateResource(context.Background(), r); err != nil {
 		t.Fatal("UpdateResource", err)
 	}
@@ -203,9 +203,9 @@ func TestModel_HasAnyResource(t *testing.T) {
 	tx, _ := b.NewTransaction()
 
 	// Request that should succeed.
-	ok, err := tx.HasAnyResource(context.Background(), []accountserver.FindResourceRequest{
-		{Type: accountserver.ResourceTypeEmail, Name: "foo"},
-		{Type: accountserver.ResourceTypeEmail, Name: testUser1},
+	ok, err := tx.HasAnyResource(context.Background(), []as.FindResourceRequest{
+		{Type: as.ResourceTypeEmail, Name: "foo"},
+		{Type: as.ResourceTypeEmail, Name: testUser1},
 	})
 	if err != nil {
 		t.Fatal("HasAnyResource", err)
@@ -215,8 +215,8 @@ func TestModel_HasAnyResource(t *testing.T) {
 	}
 
 	// Request that should fail (bad resource type).
-	ok, err = tx.HasAnyResource(context.Background(), []accountserver.FindResourceRequest{
-		{Type: accountserver.ResourceTypeDatabase, Name: testUser1},
+	ok, err = tx.HasAnyResource(context.Background(), []as.FindResourceRequest{
+		{Type: as.ResourceTypeDatabase, Name: testUser1},
 	})
 	if err != nil {
 		t.Fatal("HasAnyResource", err)
@@ -262,9 +262,9 @@ func TestModel_SetUserEncryptionKeys_Add(t *testing.T) {
 	defer stop()
 
 	tx, _ := b.NewTransaction()
-	keys := []*accountserver.UserEncryptionKey{
+	keys := []*as.UserEncryptionKey{
 		{
-			ID:  accountserver.UserEncryptionKeyMainID,
+			ID:  as.UserEncryptionKeyMainID,
 			Key: []byte("very secret key"),
 		},
 	}
@@ -281,9 +281,9 @@ func TestModel_SetUserEncryptionKeys_Replace(t *testing.T) {
 	defer stop()
 
 	tx, _ := b.NewTransaction()
-	keys := []*accountserver.UserEncryptionKey{
+	keys := []*as.UserEncryptionKey{
 		{
-			ID:  accountserver.UserEncryptionKeyMainID,
+			ID:  as.UserEncryptionKeyMainID,
 			Key: []byte("very secret key"),
 		},
 	}
diff --git a/backend/resources.go b/backend/resources.go
index 5366d2269527cd2f7fc930ff8a744492261e51c5..52423dea0b614cf5eaaa2c77e5c5dd74d5184210 100644
--- a/backend/resources.go
+++ b/backend/resources.go
@@ -8,15 +8,15 @@ import (
 
 	"gopkg.in/ldap.v2"
 
-	"git.autistici.org/ai3/accountserver"
+	as "git.autistici.org/ai3/accountserver"
 )
 
 // Generic resource handler interface. One for each resource type,
 // mapping to exactly one LDAP object type.
 type resourceHandler interface {
-	GetDN(accountserver.ResourceID) (string, error)
-	ToLDAP(*accountserver.Resource) []ldap.PartialAttribute
-	FromLDAP(*ldap.Entry) (*accountserver.Resource, error)
+	GetDN(as.ResourceID) (string, error)
+	ToLDAP(*as.Resource) []ldap.PartialAttribute
+	FromLDAP(*ldap.Entry) (*as.Resource, error)
 	SearchQuery() *queryTemplate
 }
 
@@ -47,7 +47,7 @@ func (reg *resourceRegistry) dispatch(rsrcType string, f func(resourceHandler) e
 	return f(h)
 }
 
-func (reg *resourceRegistry) GetDN(id accountserver.ResourceID) (s string, err error) {
+func (reg *resourceRegistry) GetDN(id as.ResourceID) (s string, err error) {
 	err = reg.dispatch(id.Type(), func(h resourceHandler) (herr error) {
 		s, herr = h.GetDN(id)
 		return
@@ -55,7 +55,7 @@ func (reg *resourceRegistry) GetDN(id accountserver.ResourceID) (s string, err e
 	return
 }
 
-func (reg *resourceRegistry) ToLDAP(rsrc *accountserver.Resource) (attrs []ldap.PartialAttribute) {
+func (reg *resourceRegistry) ToLDAP(rsrc *as.Resource) (attrs []ldap.PartialAttribute) {
 	if err := reg.dispatch(rsrc.ID.Type(), func(h resourceHandler) error {
 		attrs = h.ToLDAP(rsrc)
 		return nil
@@ -71,13 +71,13 @@ func (reg *resourceRegistry) ToLDAP(rsrc *accountserver.Resource) (attrs []ldap.
 	return
 }
 
-func setCommonResourceAttrs(entry *ldap.Entry, rsrc *accountserver.Resource) {
+func setCommonResourceAttrs(entry *ldap.Entry, rsrc *as.Resource) {
 	rsrc.Status = entry.GetAttributeValue("status")
 	rsrc.Shard = entry.GetAttributeValue("host")
 	rsrc.OriginalShard = entry.GetAttributeValue("originalHost")
 }
 
-func (reg *resourceRegistry) FromLDAP(entry *ldap.Entry) (rsrc *accountserver.Resource, err error) {
+func (reg *resourceRegistry) FromLDAP(entry *ldap.Entry) (rsrc *as.Resource, err error) {
 	// Since we don't know what resource type to expect, we try
 	// all known handlers until one returns a valid Resource.
 	for _, h := range reg.handlers {
@@ -90,7 +90,7 @@ func (reg *resourceRegistry) FromLDAP(entry *ldap.Entry) (rsrc *accountserver.Re
 	return nil, errors.New("unknown resource")
 }
 
-func (reg *resourceRegistry) FromLDAPWithType(rsrcType string, entry *ldap.Entry) (rsrc *accountserver.Resource, err error) {
+func (reg *resourceRegistry) FromLDAPWithType(rsrcType string, entry *ldap.Entry) (rsrc *as.Resource, err error) {
 	err = reg.dispatch(rsrcType, func(h resourceHandler) (rerr error) {
 		rsrc, rerr = h.FromLDAP(entry)
 		if rerr != nil {
@@ -131,7 +131,7 @@ type emailResourceHandler struct {
 	baseDN string
 }
 
-func (h *emailResourceHandler) GetDN(id accountserver.ResourceID) (string, error) {
+func (h *emailResourceHandler) GetDN(id as.ResourceID) (string, error) {
 	if id.User() == "" {
 		return "", errors.New("unqualified resource id")
 	}
@@ -144,7 +144,7 @@ func (h *emailResourceHandler) GetDN(id accountserver.ResourceID) (string, error
 
 var errWrongObjectClass = errors.New("objectClass does not match")
 
-func (h *emailResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Resource, error) {
+func (h *emailResourceHandler) FromLDAP(entry *ldap.Entry) (*as.Resource, error) {
 	if !isObjectClass(entry, "virtualMailUser") {
 		return nil, errWrongObjectClass
 	}
@@ -155,22 +155,22 @@ func (h *emailResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Resou
 		return nil, err
 	}
 
-	return &accountserver.Resource{
-		ID: accountserver.NewResourceID(
-			accountserver.ResourceTypeEmail,
+	return &as.Resource{
+		ID: as.NewResourceID(
+			as.ResourceTypeEmail,
 			username,
 			email,
 		),
-		Type: accountserver.ResourceTypeEmail,
+		Type: as.ResourceTypeEmail,
 		Name: email,
-		Email: &accountserver.Email{
+		Email: &as.Email{
 			Aliases: entry.GetAttributeValues("mailAlternateAddr"),
 			Maildir: entry.GetAttributeValue("mailMessageStore"),
 		},
 	}, nil
 }
 
-func (h *emailResourceHandler) ToLDAP(rsrc *accountserver.Resource) []ldap.PartialAttribute {
+func (h *emailResourceHandler) ToLDAP(rsrc *as.Resource) []ldap.PartialAttribute {
 	return []ldap.PartialAttribute{
 		{Type: "objectClass", Vals: []string{"top", "virtualMailUser"}},
 		{Type: "mail", Vals: s2l(rsrc.ID.Name())},
@@ -192,31 +192,31 @@ type mailingListResourceHandler struct {
 	baseDN string
 }
 
-func (h *mailingListResourceHandler) GetDN(id accountserver.ResourceID) (string, error) {
+func (h *mailingListResourceHandler) GetDN(id as.ResourceID) (string, error) {
 	dn := replaceVars("listName=${resource},ou=Lists", map[string]string{
 		"resource": id.Name(),
 	})
 	return joinDN(dn, h.baseDN), nil
 }
 
-func (h *mailingListResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Resource, error) {
+func (h *mailingListResourceHandler) FromLDAP(entry *ldap.Entry) (*as.Resource, error) {
 	if !isObjectClass(entry, "mailingList") {
 		return nil, errWrongObjectClass
 	}
 
 	listName := entry.GetAttributeValue("listName")
-	return &accountserver.Resource{
-		ID:   accountserver.NewResourceID(accountserver.ResourceTypeMailingList, listName),
-		Type: accountserver.ResourceTypeMailingList,
+	return &as.Resource{
+		ID:   as.NewResourceID(as.ResourceTypeMailingList, listName),
+		Type: as.ResourceTypeMailingList,
 		Name: listName,
-		List: &accountserver.MailingList{
+		List: &as.MailingList{
 			Public: s2b(entry.GetAttributeValue("public")),
 			Admins: entry.GetAttributeValues("listOwner"),
 		},
 	}, nil
 }
 
-func (h *mailingListResourceHandler) ToLDAP(rsrc *accountserver.Resource) []ldap.PartialAttribute {
+func (h *mailingListResourceHandler) ToLDAP(rsrc *as.Resource) []ldap.PartialAttribute {
 	return []ldap.PartialAttribute{
 		{Type: "objectClass", Vals: []string{"top", "mailingList"}},
 		{Type: "listName", Vals: s2l(rsrc.ID.Name())},
@@ -238,7 +238,7 @@ type websiteResourceHandler struct {
 	baseDN string
 }
 
-func (h *websiteResourceHandler) GetDN(id accountserver.ResourceID) (string, error) {
+func (h *websiteResourceHandler) GetDN(id as.ResourceID) (string, error) {
 	if id.User() == "" {
 		return "", errors.New("unqualified resource id")
 	}
@@ -250,7 +250,7 @@ func (h *websiteResourceHandler) GetDN(id accountserver.ResourceID) (string, err
 	return joinDN(dn, h.baseDN), nil
 }
 
-func (h *websiteResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Resource, error) {
+func (h *websiteResourceHandler) FromLDAP(entry *ldap.Entry) (*as.Resource, error) {
 	if !isObjectClass(entry, "subSite") {
 		return nil, errWrongObjectClass
 	}
@@ -259,21 +259,21 @@ func (h *websiteResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Res
 	parentSite := entry.GetAttributeValue("parentSite")
 	name := fmt.Sprintf("%s/%s", parentSite, alias)
 	url := fmt.Sprintf("https://www.%s/%s/", parentSite, alias)
-	uid, _ := strconv.Atoi(entry.GetAttributeValue(uidNumberLDAPAttr))
+	uid, _ := strconv.Atoi(entry.GetAttributeValue(uidNumberLDAPAttr)) // nolint
 
 	username, err := getParentRDN(entry.DN, "uid")
 	if err != nil {
 		return nil, err
 	}
-	return &accountserver.Resource{
-		ID: accountserver.NewResourceID(
-			accountserver.ResourceTypeWebsite,
+	return &as.Resource{
+		ID: as.NewResourceID(
+			as.ResourceTypeWebsite,
 			username,
 			alias,
 		),
-		Type: accountserver.ResourceTypeWebsite,
+		Type: as.ResourceTypeWebsite,
 		Name: name,
-		Website: &accountserver.Website{
+		Website: &as.Website{
 			URL:          url,
 			UID:          uid,
 			ParentDomain: parentSite,
@@ -284,7 +284,7 @@ func (h *websiteResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Res
 	}, nil
 }
 
-func (h *websiteResourceHandler) ToLDAP(rsrc *accountserver.Resource) []ldap.PartialAttribute {
+func (h *websiteResourceHandler) ToLDAP(rsrc *as.Resource) []ldap.PartialAttribute {
 	return []ldap.PartialAttribute{
 		{Type: "objectClass", Vals: []string{"top", "subSite"}},
 		{Type: "alias", Vals: s2l(rsrc.ID.Name())},
@@ -308,7 +308,7 @@ type domainResourceHandler struct {
 	baseDN string
 }
 
-func (h *domainResourceHandler) GetDN(id accountserver.ResourceID) (string, error) {
+func (h *domainResourceHandler) GetDN(id as.ResourceID) (string, error) {
 	if id.User() == "" {
 		return "", errors.New("unqualified resource id")
 	}
@@ -320,7 +320,7 @@ func (h *domainResourceHandler) GetDN(id accountserver.ResourceID) (string, erro
 	return joinDN(dn, h.baseDN), nil
 }
 
-func (h *domainResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Resource, error) {
+func (h *domainResourceHandler) FromLDAP(entry *ldap.Entry) (*as.Resource, error) {
 	if !isObjectClass(entry, "virtualHost") {
 		return nil, errWrongObjectClass
 	}
@@ -330,17 +330,17 @@ func (h *domainResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Reso
 	if err != nil {
 		return nil, err
 	}
-	uid, _ := strconv.Atoi(entry.GetAttributeValue(uidNumberLDAPAttr))
+	uid, _ := strconv.Atoi(entry.GetAttributeValue(uidNumberLDAPAttr)) // nolint
 
-	return &accountserver.Resource{
-		ID: accountserver.NewResourceID(
-			accountserver.ResourceTypeDomain,
+	return &as.Resource{
+		ID: as.NewResourceID(
+			as.ResourceTypeDomain,
 			username,
 			cn,
 		),
-		Type: accountserver.ResourceTypeDomain,
+		Type: as.ResourceTypeDomain,
 		Name: cn,
-		Website: &accountserver.Website{
+		Website: &as.Website{
 			URL:          fmt.Sprintf("https://%s/", cn),
 			UID:          uid,
 			Options:      entry.GetAttributeValues("option"),
@@ -350,7 +350,7 @@ func (h *domainResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Reso
 	}, nil
 }
 
-func (h *domainResourceHandler) ToLDAP(rsrc *accountserver.Resource) []ldap.PartialAttribute {
+func (h *domainResourceHandler) ToLDAP(rsrc *as.Resource) []ldap.PartialAttribute {
 	return []ldap.PartialAttribute{
 		{Type: "objectClass", Vals: []string{"top", "virtualHost"}},
 		{Type: "cn", Vals: s2l(rsrc.ID.Name())},
@@ -373,7 +373,7 @@ type webdavResourceHandler struct {
 	baseDN string
 }
 
-func (h *webdavResourceHandler) GetDN(id accountserver.ResourceID) (string, error) {
+func (h *webdavResourceHandler) GetDN(id as.ResourceID) (string, error) {
 	if id.User() == "" {
 		return "", errors.New("unqualified resource id")
 	}
@@ -385,7 +385,7 @@ func (h *webdavResourceHandler) GetDN(id accountserver.ResourceID) (string, erro
 	return joinDN(dn, h.baseDN), nil
 }
 
-func (h *webdavResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Resource, error) {
+func (h *webdavResourceHandler) FromLDAP(entry *ldap.Entry) (*as.Resource, error) {
 	if !isObjectClass(entry, "ftpAccount") {
 		return nil, errWrongObjectClass
 	}
@@ -395,23 +395,23 @@ func (h *webdavResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Reso
 	if err != nil {
 		return nil, err
 	}
-	uid, _ := strconv.Atoi(entry.GetAttributeValue(uidNumberLDAPAttr))
-	return &accountserver.Resource{
-		ID: accountserver.NewResourceID(
-			accountserver.ResourceTypeDAV,
+	uid, _ := strconv.Atoi(entry.GetAttributeValue(uidNumberLDAPAttr)) // nolint
+	return &as.Resource{
+		ID: as.NewResourceID(
+			as.ResourceTypeDAV,
 			username,
 			name,
 		),
-		Type: accountserver.ResourceTypeDAV,
+		Type: as.ResourceTypeDAV,
 		Name: name,
-		DAV: &accountserver.WebDAV{
+		DAV: &as.WebDAV{
 			Homedir: entry.GetAttributeValue("homeDirectory"),
 			UID:     uid,
 		},
 	}, nil
 }
 
-func (h *webdavResourceHandler) ToLDAP(rsrc *accountserver.Resource) []ldap.PartialAttribute {
+func (h *webdavResourceHandler) ToLDAP(rsrc *as.Resource) []ldap.PartialAttribute {
 	return []ldap.PartialAttribute{
 		{Type: "objectClass", Vals: []string{"top", "person", "posixAccount", "shadowAccount", "organizationalPerson", "inetOrgPerson", "ftpAccount"}},
 		{Type: "ftpname", Vals: s2l(rsrc.ID.Name())},
@@ -438,7 +438,7 @@ type databaseResourceHandler struct {
 	baseDN string
 }
 
-func makeDatabaseResourceID(dn string) (rsrcID, parentID accountserver.ResourceID, err error) {
+func makeDatabaseResourceID(dn string) (rsrcID, parentID as.ResourceID, err error) {
 	parsed, perr := ldap.ParseDN(dn)
 	if perr != nil {
 		err = perr
@@ -457,17 +457,17 @@ func makeDatabaseResourceID(dn string) (rsrcID, parentID accountserver.ResourceI
 	// The username is the 3rd component.
 	username := parsed.RDNs[2].Attributes[0].Value
 
-	rsrcID = accountserver.NewResourceID(
-		accountserver.ResourceTypeDatabase,
+	rsrcID = as.NewResourceID(
+		as.ResourceTypeDatabase,
 		username,
 		encParent,
 		dbname,
 	)
-	var parentType = accountserver.ResourceTypeWebsite
+	var parentType = as.ResourceTypeWebsite
 	if parsed.RDNs[1].Attributes[0].Type == "cn" {
-		parentType = accountserver.ResourceTypeDomain
+		parentType = as.ResourceTypeDomain
 	}
-	parentID = accountserver.NewResourceID(
+	parentID = as.NewResourceID(
 		parentType,
 		username,
 		parentName,
@@ -475,7 +475,7 @@ func makeDatabaseResourceID(dn string) (rsrcID, parentID accountserver.ResourceI
 	return
 }
 
-func (h *databaseResourceHandler) GetDN(id accountserver.ResourceID) (string, error) {
+func (h *databaseResourceHandler) GetDN(id as.ResourceID) (string, error) {
 	if id.User() == "" || len(id.Parts) < 4 {
 		return "", errors.New("unqualified resource id")
 	}
@@ -497,7 +497,7 @@ func (h *databaseResourceHandler) GetDN(id accountserver.ResourceID) (string, er
 	return joinDN(dn, h.baseDN), nil
 }
 
-func (h *databaseResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Resource, error) {
+func (h *databaseResourceHandler) FromLDAP(entry *ldap.Entry) (*as.Resource, error) {
 	if !isObjectClass(entry, "dbMysql") {
 		return nil, errWrongObjectClass
 	}
@@ -507,19 +507,19 @@ func (h *databaseResourceHandler) FromLDAP(entry *ldap.Entry) (*accountserver.Re
 	if err != nil {
 		return nil, err
 	}
-	return &accountserver.Resource{
+	return &as.Resource{
 		ID:       rsrcID,
-		Type:     accountserver.ResourceTypeDatabase,
+		Type:     as.ResourceTypeDatabase,
 		ParentID: parentID,
 		Name:     name,
-		Database: &accountserver.Database{
+		Database: &as.Database{
 			DBUser:            entry.GetAttributeValue("dbuser"),
 			CleartextPassword: entry.GetAttributeValue("clearPassword"),
 		},
 	}, nil
 }
 
-func (h *databaseResourceHandler) ToLDAP(rsrc *accountserver.Resource) []ldap.PartialAttribute {
+func (h *databaseResourceHandler) ToLDAP(rsrc *as.Resource) []ldap.PartialAttribute {
 	return []ldap.PartialAttribute{
 		{Type: "objectClass", Vals: []string{"top", "dbMysql"}},
 		{Type: "dbname", Vals: s2l(rsrc.ID.Name())},
diff --git a/backend/resources_test.go b/backend/resources_test.go
index 362c09d620855032c9331c79b2e0172bc130f444..4f4d4bb6ce84738a8124ec4250567810f72e5c5f 100644
--- a/backend/resources_test.go
+++ b/backend/resources_test.go
@@ -6,7 +6,7 @@ import (
 	"github.com/go-test/deep"
 	"gopkg.in/ldap.v2"
 
-	"git.autistici.org/ai3/accountserver"
+	as "git.autistici.org/ai3/accountserver"
 )
 
 func TestEmailResource_FromLDAP(t *testing.T) {
@@ -24,21 +24,21 @@ func TestEmailResource_FromLDAP(t *testing.T) {
 	)
 
 	reg := newResourceRegistry()
-	reg.register(accountserver.ResourceTypeEmail, &emailResourceHandler{baseDN: "dc=example,dc=com"})
+	reg.register(as.ResourceTypeEmail, &emailResourceHandler{baseDN: "dc=example,dc=com"})
 
 	r, err := reg.FromLDAP(entry)
 	if err != nil {
 		t.Fatal("FromLDAP", err)
 	}
 
-	expected := &accountserver.Resource{
-		ID:            accountserver.NewResourceID("email", "test@investici.org", "test@investici.org"),
+	expected := &as.Resource{
+		ID:            as.NewResourceID("email", "test@investici.org", "test@investici.org"),
 		Type:          "email",
 		Name:          "test@investici.org",
 		Status:        "active",
 		Shard:         "host1",
 		OriginalShard: "host1",
-		Email: &accountserver.Email{
+		Email: &as.Email{
 			Aliases: []string{"test2@investici.org", "test3@investici.org"},
 			Maildir: "test/store",
 		},