diff --git a/Makefile b/Makefile
index 3bea273b10..0cd0d8fe6b 100644
--- a/Makefile
+++ b/Makefile
@@ -292,7 +292,7 @@ fmt-check:
 checks: checks-frontend checks-backend
 
 .PHONY: checks-frontend
-checks-frontend: svg-check
+checks-frontend: lockfile-check svg-check
 
 .PHONY: checks-backend
 checks-backend: test-vendor swagger-check swagger-validate
@@ -700,6 +700,17 @@ svg-check: svg
 		exit 1; \
 	fi
 
+.PHONY: lockfile-check
+lockfile-check:
+	npm install --package-lock-only
+	@diff=$$(git diff package-lock.json); \
+	if [ -n "$$diff" ]; then \
+		echo "package-lock.json is inconsistent with package.json"; \
+		echo "Please run 'npm install --package-lock-only' and commit the result:"; \
+		echo "$${diff}"; \
+		exit 1; \
+	fi
+
 .PHONY: update-translations
 update-translations:
 	mkdir -p ./translations
diff --git a/docs/content/doc/usage/reverse-proxies.en-us.md b/docs/content/doc/usage/reverse-proxies.en-us.md
index c782f1ce5c..4567b09d22 100644
--- a/docs/content/doc/usage/reverse-proxies.en-us.md
+++ b/docs/content/doc/usage/reverse-proxies.en-us.md
@@ -128,6 +128,7 @@ This error indicates nginx is configured to restrict the file upload size.
 
 In your nginx config file containing your Gitea proxy directive, find the `location { ... }` block for Gitea and add the line
 `client_max_body_size 16M;` to set this limit to 16 megabytes or any other number of choice.
+If you use Git LFS, this will also limit the size of the largest file you will be able to push.
 
 
 ## Apache HTTPD
diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go
index 75776f1e0e..9e09134662 100644
--- a/models/auth/webauthn.go
+++ b/models/auth/webauthn.go
@@ -6,7 +6,7 @@ package auth
 
 import (
 	"context"
-	"encoding/base64"
+	"encoding/base32"
 	"fmt"
 	"strings"
 
@@ -94,7 +94,7 @@ type WebAuthnCredentialList []*WebAuthnCredential
 func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential {
 	creds := make([]webauthn.Credential, 0, len(list))
 	for _, cred := range list {
-		credID, _ := base64.RawStdEncoding.DecodeString(cred.CredentialID)
+		credID, _ := base32.HexEncoding.DecodeString(cred.CredentialID)
 		creds = append(creds, webauthn.Credential{
 			ID:              credID,
 			PublicKey:       cred.PublicKey,
@@ -164,13 +164,13 @@ func HasWebAuthnRegistrationsByUID(uid int64) (bool, error) {
 }
 
 // GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID
-func GetWebAuthnCredentialByCredID(credID string) (*WebAuthnCredential, error) {
-	return getWebAuthnCredentialByCredID(db.DefaultContext, credID)
+func GetWebAuthnCredentialByCredID(userID int64, credID string) (*WebAuthnCredential, error) {
+	return getWebAuthnCredentialByCredID(db.DefaultContext, userID, credID)
 }
 
-func getWebAuthnCredentialByCredID(ctx context.Context, credID string) (*WebAuthnCredential, error) {
+func getWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID string) (*WebAuthnCredential, error) {
 	cred := new(WebAuthnCredential)
-	if found, err := db.GetEngine(ctx).Where("credential_id = ?", credID).Get(cred); err != nil {
+	if found, err := db.GetEngine(ctx).Where("user_id = ? AND credential_id = ?", userID, credID).Get(cred); err != nil {
 		return nil, err
 	} else if !found {
 		return nil, ErrWebAuthnCredentialNotExist{CredentialID: credID}
@@ -187,7 +187,7 @@ func createCredential(ctx context.Context, userID int64, name string, cred *weba
 	c := &WebAuthnCredential{
 		UserID:          userID,
 		Name:            name,
-		CredentialID:    base64.RawStdEncoding.EncodeToString(cred.ID),
+		CredentialID:    base32.HexEncoding.EncodeToString(cred.ID),
 		PublicKey:       cred.PublicKey,
 		AttestationType: cred.AttestationType,
 		AAGUID:          cred.Authenticator.AAGUID,
diff --git a/models/auth/webauthn_test.go b/models/auth/webauthn_test.go
index 572636dbbf..216bf11080 100644
--- a/models/auth/webauthn_test.go
+++ b/models/auth/webauthn_test.go
@@ -5,7 +5,7 @@
 package auth
 
 import (
-	"encoding/base64"
+	"encoding/base32"
 	"testing"
 
 	"code.gitea.io/gitea/models/unittest"
@@ -61,7 +61,7 @@ func TestCreateCredential(t *testing.T) {
 	res, err := CreateCredential(1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")})
 	assert.NoError(t, err)
 	assert.Equal(t, "WebAuthn Created Credential", res.Name)
-	bs, err := base64.RawStdEncoding.DecodeString(res.CredentialID)
+	bs, err := base32.HexEncoding.DecodeString(res.CredentialID)
 	assert.NoError(t, err)
 	assert.Equal(t, []byte("Test"), bs)
 
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 4ee2bc839f..5aaf283bd3 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -368,6 +368,8 @@ var migrations = []Migration{
 	NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
 	// v207 -> v208
 	NewMigration("Add webauthn table and migrate u2f data to webauthn", addWebAuthnCred),
+	// v208 -> v209
+	NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive", useBase32HexForCredIDInWebAuthnCredential),
 }
 
 // GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v208.go b/models/migrations/v208.go
new file mode 100644
index 0000000000..04bb981a4e
--- /dev/null
+++ b/models/migrations/v208.go
@@ -0,0 +1,51 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"encoding/base32"
+	"encoding/base64"
+
+	"xorm.io/xorm"
+)
+
+func useBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
+
+	// Create webauthnCredential table
+	type webauthnCredential struct {
+		ID           int64  `xorm:"pk autoincr"`
+		CredentialID string `xorm:"INDEX"`
+	}
+	if err := x.Sync2(&webauthnCredential{}); err != nil {
+		return err
+	}
+
+	var start int
+	regs := make([]*webauthnCredential, 0, 50)
+	for {
+		err := x.OrderBy("id").Limit(50, start).Find(&regs)
+		if err != nil {
+			return err
+		}
+
+		for _, reg := range regs {
+			credID, _ := base64.RawStdEncoding.DecodeString(reg.CredentialID)
+			reg.CredentialID = base32.HexEncoding.EncodeToString(credID)
+
+			_, err := x.Update(reg)
+			if err != nil {
+				return err
+			}
+		}
+
+		if len(regs) < 50 {
+			break
+		}
+		start += 50
+		regs = regs[:0]
+	}
+
+	return nil
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index e903e4c534..d8398f6d9f 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -748,10 +748,9 @@ passcode_invalid = The passcode is incorrect. Try again.
 twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once!
 twofa_failed_get_secret = Failed to get secret.
 
-webauthn_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a> standard.
+webauthn_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a> standard.
 webauthn_register_key = Add Security Key
 webauthn_nickname = Nickname
-webauthn_press_button = Press the button on your security key to register it.
 webauthn_delete_key = Remove Security Key
 webauthn_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue?
 
diff --git a/package-lock.json b/package-lock.json
index c9151da16c..3d62eb1ab2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,6 +4,7 @@
   "requires": true,
   "packages": {
     "": {
+      "name": "gitea",
       "license": "MIT",
       "dependencies": {
         "@claviska/jquery-minicolors": "2.3.6",
@@ -12,7 +13,7 @@
         "codemirror": "5.64.0",
         "css-loader": "6.5.1",
         "dropzone": "6.0.0-beta.2",
-        "easymde": "2.15.0",
+        "easymde": "2.16.1",
         "esbuild-loader": "2.16.0",
         "escape-goat": "4.0.0",
         "fast-glob": "3.2.7",
@@ -1305,9 +1306,9 @@
       }
     },
     "node_modules/@types/codemirror": {
-      "version": "0.0.109",
-      "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.109.tgz",
-      "integrity": "sha512-cSdiHeeLjvGn649lRTNeYrVCDOgDrtP+bDDSFDd1TF+i0jKGPDRozno2NOJ9lTniso+taiv4kiVS8dgM8Jm5lg==",
+      "version": "5.60.5",
+      "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",
+      "integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==",
       "dependencies": {
         "@types/tern": "*"
       }
@@ -1380,9 +1381,9 @@
       "dev": true
     },
     "node_modules/@types/marked": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/@types/marked/-/marked-2.0.5.tgz",
-      "integrity": "sha512-shRZ7XnYFD/8n8zSjKvFdto1QNSf4tONZIlNEZGrJe8GsOE8DL/hG1Hbl8gZlfLnjS7+f5tZGIaTgfpyW38h4w=="
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.1.tgz",
+      "integrity": "sha512-ZigEmCWdNUU7IjZEuQ/iaimYdDHWHfTe3kg8ORfKjyGYd9RWumPoOJRQXB0bO+XLkNwzCthW3wUIQtANaEZ1ag=="
     },
     "node_modules/@types/minimist": {
       "version": "1.2.2",
@@ -3780,15 +3781,15 @@
       }
     },
     "node_modules/easymde": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/easymde/-/easymde-2.15.0.tgz",
-      "integrity": "sha512-9jMRIVvKt1d0UjRN45yotUYECAM4xvw0TTAQw8sYDONP++keWJVnd8Xrn+V+vQEN/v9/X0SWEoo1rFSgCooGpw==",
+      "version": "2.16.1",
+      "resolved": "https://registry.npmjs.org/easymde/-/easymde-2.16.1.tgz",
+      "integrity": "sha512-FihYgjRsKfhGNk89SHSqxKLC4aJ1kfybPWW6iAmtb5GnXu+tnFPSzSaGBmk1RRlCuhFSjhF0SnIMGVPjEzkr6g==",
       "dependencies": {
-        "@types/codemirror": "0.0.109",
-        "@types/marked": "^2.0.2",
-        "codemirror": "^5.61.0",
+        "@types/codemirror": "^5.60.4",
+        "@types/marked": "^4.0.1",
+        "codemirror": "^5.63.1",
         "codemirror-spell-checker": "1.1.2",
-        "marked": "^2.0.3"
+        "marked": "^4.0.10"
       }
     },
     "node_modules/editorconfig-checker": {
@@ -7356,14 +7357,14 @@
       }
     },
     "node_modules/marked": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
-      "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==",
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz",
+      "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==",
       "bin": {
-        "marked": "bin/marked"
+        "marked": "bin/marked.js"
       },
       "engines": {
-        "node": ">= 10"
+        "node": ">= 12"
       }
     },
     "node_modules/mathml-tag-names": {
@@ -11526,9 +11527,9 @@
       }
     },
     "@types/codemirror": {
-      "version": "0.0.109",
-      "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.109.tgz",
-      "integrity": "sha512-cSdiHeeLjvGn649lRTNeYrVCDOgDrtP+bDDSFDd1TF+i0jKGPDRozno2NOJ9lTniso+taiv4kiVS8dgM8Jm5lg==",
+      "version": "5.60.5",
+      "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",
+      "integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==",
       "requires": {
         "@types/tern": "*"
       }
@@ -11601,9 +11602,9 @@
       "dev": true
     },
     "@types/marked": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/@types/marked/-/marked-2.0.5.tgz",
-      "integrity": "sha512-shRZ7XnYFD/8n8zSjKvFdto1QNSf4tONZIlNEZGrJe8GsOE8DL/hG1Hbl8gZlfLnjS7+f5tZGIaTgfpyW38h4w=="
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.1.tgz",
+      "integrity": "sha512-ZigEmCWdNUU7IjZEuQ/iaimYdDHWHfTe3kg8ORfKjyGYd9RWumPoOJRQXB0bO+XLkNwzCthW3wUIQtANaEZ1ag=="
     },
     "@types/minimist": {
       "version": "1.2.2",
@@ -13474,15 +13475,15 @@
       }
     },
     "easymde": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/easymde/-/easymde-2.15.0.tgz",
-      "integrity": "sha512-9jMRIVvKt1d0UjRN45yotUYECAM4xvw0TTAQw8sYDONP++keWJVnd8Xrn+V+vQEN/v9/X0SWEoo1rFSgCooGpw==",
+      "version": "2.16.1",
+      "resolved": "https://registry.npmjs.org/easymde/-/easymde-2.16.1.tgz",
+      "integrity": "sha512-FihYgjRsKfhGNk89SHSqxKLC4aJ1kfybPWW6iAmtb5GnXu+tnFPSzSaGBmk1RRlCuhFSjhF0SnIMGVPjEzkr6g==",
       "requires": {
-        "@types/codemirror": "0.0.109",
-        "@types/marked": "^2.0.2",
-        "codemirror": "^5.61.0",
+        "@types/codemirror": "^5.60.4",
+        "@types/marked": "^4.0.1",
+        "codemirror": "^5.63.1",
         "codemirror-spell-checker": "1.1.2",
-        "marked": "^2.0.3"
+        "marked": "^4.0.10"
       }
     },
     "editorconfig-checker": {
@@ -16117,9 +16118,9 @@
       "dev": true
     },
     "marked": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
-      "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA=="
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz",
+      "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw=="
     },
     "mathml-tag-names": {
       "version": "2.1.3",
diff --git a/package.json b/package.json
index 2f8ac8df53..86e8b9aedd 100644
--- a/package.json
+++ b/package.json
@@ -1,4 +1,5 @@
 {
+  "name": "gitea",
   "license": "MIT",
   "private": true,
   "type": "module",
diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go
index 50dcb919e5..b9e8de2ac0 100644
--- a/routers/web/auth/webauthn.go
+++ b/routers/web/auth/webauthn.go
@@ -5,7 +5,7 @@
 package auth
 
 import (
-	"encoding/base64"
+	"encoding/base32"
 	"errors"
 	"net/http"
 
@@ -131,7 +131,7 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) {
 	}
 
 	// Success! Get the credential and update the sign count with the new value we received.
-	dbCred, err := auth.GetWebAuthnCredentialByCredID(base64.RawStdEncoding.EncodeToString(cred.ID))
+	dbCred, err := auth.GetWebAuthnCredentialByCredID(user.ID, base32.HexEncoding.EncodeToString(cred.ID))
 	if err != nil {
 		ctx.ServerError("GetWebAuthnCredentialByCredID", err)
 		return
diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go
index 8d28de8c98..7e2fc7283b 100644
--- a/routers/web/user/setting/security/webauthn.go
+++ b/routers/web/user/setting/security/webauthn.go
@@ -38,9 +38,9 @@ func WebAuthnRegister(ctx *context.Context) {
 		return
 	}
 
-	_ = ctx.Session.Delete("registration")
-	if err := ctx.Session.Set("WebauthnName", form.Name); err != nil {
-		ctx.ServerError("Unable to set session key for WebauthnName", err)
+	_ = ctx.Session.Delete("webauthnRegistration")
+	if err := ctx.Session.Set("webauthnName", form.Name); err != nil {
+		ctx.ServerError("Unable to set session key for webauthnName", err)
 		return
 	}
 
@@ -51,7 +51,7 @@ func WebAuthnRegister(ctx *context.Context) {
 	}
 
 	// Save the session data as marshaled JSON
-	if err = ctx.Session.Set("registration", sessionData); err != nil {
+	if err = ctx.Session.Set("webauthnRegistration", sessionData); err != nil {
 		ctx.ServerError("Unable to set session", err)
 		return
 	}
@@ -61,20 +61,20 @@ func WebAuthnRegister(ctx *context.Context) {
 
 // WebauthnRegisterPost receives the response of the security key
 func WebauthnRegisterPost(ctx *context.Context) {
-	name, ok := ctx.Session.Get("WebauthnName").(string)
+	name, ok := ctx.Session.Get("webauthnName").(string)
 	if !ok || name == "" {
-		ctx.ServerError("Get WebauthnName", errors.New("no WebauthnName"))
+		ctx.ServerError("Get webauthnName", errors.New("no webauthnName"))
 		return
 	}
 
 	// Load the session data
-	sessionData, ok := ctx.Session.Get("registration").(*webauthn.SessionData)
+	sessionData, ok := ctx.Session.Get("webauthnRegistration").(*webauthn.SessionData)
 	if !ok || sessionData == nil {
 		ctx.ServerError("Get registration", errors.New("no registration"))
 		return
 	}
 	defer func() {
-		_ = ctx.Session.Delete("registration")
+		_ = ctx.Session.Delete("webauthnRegistration")
 	}()
 
 	// Verify that the challenge succeeded
@@ -103,6 +103,8 @@ func WebauthnRegisterPost(ctx *context.Context) {
 		ctx.ServerError("CreateCredential", err)
 		return
 	}
+	_ = ctx.Session.Delete("webauthnName")
+
 	ctx.JSON(http.StatusCreated, cred)
 }
 
diff --git a/templates/user/auth/webauthn_error.tmpl b/templates/user/auth/webauthn_error.tmpl
index be46ee42a0..6f2980df7c 100644
--- a/templates/user/auth/webauthn_error.tmpl
+++ b/templates/user/auth/webauthn_error.tmpl
@@ -12,7 +12,7 @@
 			<div class="hide" data-webauthn-error-msg="duplicated"><p>{{.i18n.Tr "webauthn_error_duplicated"}}</div>
 			<div class="hide" data-webauthn-error-msg="empty"><p>{{.i18n.Tr "webauthn_error_empty"}}</div>
 			<div class="hide" data-webauthn-error-msg="timeout"><p>{{.i18n.Tr "webauthn_error_timeout"}}</div>
-			<div class="hide" data-webauthn-error-msg="0"></div>
+			<div class="hide" data-webauthn-error-msg="general"></div>
 		</div>
 	</div>
 	<div class="actions">
diff --git a/templates/user/settings/security/webauthn.tmpl b/templates/user/settings/security/webauthn.tmpl
index be8f8cccda..d447ec04b3 100644
--- a/templates/user/settings/security/webauthn.tmpl
+++ b/templates/user/settings/security/webauthn.tmpl
@@ -28,16 +28,6 @@
 	</div>
 </div>
 
-<div class="ui small modal" id="register-device">
-	<div class="header">{{.i18n.Tr "settings.webauthn_register_key"}}</div>
-	<div class="content">
-		<i class="notched spinner loading icon"></i> {{.i18n.Tr "settings.webauthn_press_button"}}
-	</div>
-	<div class="actions">
-		<div class="ui cancel button">{{.i18n.Tr "cancel"}}</div>
-	</div>
-</div>
-
 {{template "user/auth/webauthn_error" .}}
 
 <div class="ui small basic delete modal" id="delete-registration">
diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js
index 5b580e7949..bc221d037f 100644
--- a/web_src/js/features/user-auth-webauthn.js
+++ b/web_src/js/features/user-auth-webauthn.js
@@ -24,7 +24,7 @@ export function initUserAuthWebAuthn() {
         .then((credential) => {
           verifyAssertion(credential);
         }).catch((err) => {
-          webAuthnError(0, err.message);
+          webAuthnError('general', err.message);
         });
     }).fail(() => {
       webAuthnError('unknown');
@@ -113,11 +113,16 @@ function webauthnRegistered(newCredential) {
 
 function webAuthnError(errorType, message) {
   $('#webauthn-error [data-webauthn-error-msg]').hide();
-  if (errorType === 0 && message && message.length > 1) {
-    $(`#webauthn-error [data-webauthn-error-msg=0]`).text(message);
-    $(`#webauthn-error [data-webauthn-error-msg=0]`).show();
+  const $errorGeneral = $(`#webauthn-error [data-webauthn-error-msg=general]`);
+  if (errorType === 'general') {
+    $errorGeneral.show().text(message || 'unknown error');
   } else {
-    $(`#webauthn-error [data-webauthn-error-msg=${errorType}]`).show();
+    const $errorTyped = $(`#webauthn-error [data-webauthn-error-msg=${errorType}]`);
+    if ($errorTyped.length) {
+      $errorTyped.show();
+    } else {
+      $errorGeneral.show().text(`unknown error type: ${errorType}`);
+    }
   }
   $('#webauthn-error').modal('show');
 }
@@ -149,7 +154,6 @@ export function initUserAuthWebAuthnRegister() {
     return;
   }
 
-  $('#register-device').modal({allowMultiple: false});
   $('#webauthn-error').modal({allowMultiple: false});
   $('#register-webauthn').on('click', (e) => {
     e.preventDefault();
@@ -167,7 +171,6 @@ function webAuthnRegisterRequest() {
     name: $('#nickname').val(),
   }).done((makeCredentialOptions) => {
     $('#nickname').closest('div.field').removeClass('error');
-    $('#register-device').modal('show');
 
     makeCredentialOptions.publicKey.challenge = decode(makeCredentialOptions.publicKey.challenge);
     makeCredentialOptions.publicKey.user.id = decode(makeCredentialOptions.publicKey.user.id);
@@ -185,7 +188,7 @@ function webAuthnRegisterRequest() {
           webAuthnError('unknown');
           return;
         }
-        webAuthnError(0, err);
+        webAuthnError('general', err.message);
       });
   }).fail((xhr) => {
     if (xhr.status === 409) {