mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-08 12:02:56 +02:00
feat(user): allow renaming security keys (webauthn/passkey) (#39413)
Closes https://github.com/go-gitea/gitea/issues/39287 Security key nicknames could only be set at registration, so a skipped nickname left an auto-generated hex name until the key was re-registered. Each key now has a Rename button opening a dialog with the current nickname. A nickname used by another of the user's keys (case-insensitive) or a blank nickname is rejected. Renames are recorded as `user:webauth:rename` audit events. Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
17 files changed
+142
-21
No files matched your search
@@ -84,6 +84,7 @@ var (
|
||||
UserTwoFactorRegenerate = define("user:twofactor:regenerate", "Regenerated two-factor authentication secret for user {scope}.")
|
||||
UserTwoFactorDisable = define("user:twofactor:disable", "Disabled two-factor authentication for user {scope}.")
|
||||
UserWebAuthAdd = define("user:webauth:add", "Added WebAuthn key {credential} for user {scope}.")
|
||||
UserWebAuthRename = define("user:webauth:rename", "Renamed WebAuthn key {previous_credential} of user {scope} to {credential}.")
|
||||
UserWebAuthRemove = define("user:webauth:remove", "Removed WebAuthn key {credential} from user {scope}.")
|
||||
UserExternalLoginAdd = define("user:externallogin:add", "Added external login {external_id} for user {scope} using provider {provider}.")
|
||||
UserExternalLoginRemove = define("user:externallogin:remove", "Removed external login from authentication source {auth_source_id} for user {scope}.")
|
||||
|
||||
+22
-4
@@ -150,9 +150,9 @@ func GetWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*
|
||||
}
|
||||
|
||||
// GetWebAuthnCredentialByID returns WebAuthn credential by id
|
||||
func GetWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) {
|
||||
func GetWebAuthnCredentialByID(ctx context.Context, uid, id int64) (*WebAuthnCredential, error) {
|
||||
cred := new(WebAuthnCredential)
|
||||
if found, err := db.GetEngine(ctx).ID(id).Get(cred); err != nil {
|
||||
if found, err := db.GetEngine(ctx).Where("user_id = ?", uid).ID(id).Get(cred); err != nil {
|
||||
return nil, err
|
||||
} else if !found {
|
||||
return nil, ErrWebAuthnCredentialNotExist{ID: id}
|
||||
@@ -195,8 +195,26 @@ func CreateCredential(ctx context.Context, userID int64, name string, cred *weba
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// RenameCredential renames the user's WebAuthnCredential, names are unique per user regardless of letter case
|
||||
func RenameCredential(ctx context.Context, uid, id int64, name string) (bool, error) {
|
||||
used, err := db.GetEngine(ctx).Where("user_id = ? AND lower_name = ? AND id != ?", uid, strings.ToLower(name), id).Exist(&WebAuthnCredential{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if used {
|
||||
return false, util.ErrorWrapTranslatable(
|
||||
util.NewAlreadyExistErrorf("WebAuthn credential name already exists [uid: %d, name: %s]", uid, name),
|
||||
"settings.webauthn_nickname_been_used",
|
||||
)
|
||||
}
|
||||
updated, err := db.GetEngine(ctx).ID(id).Where("user_id=? AND `name`<>?", uid, name).Cols("name", "lower_name").Update(&WebAuthnCredential{
|
||||
Name: name,
|
||||
LowerName: strings.ToLower(name),
|
||||
})
|
||||
return updated > 0, err
|
||||
}
|
||||
|
||||
// DeleteCredential will delete WebAuthnCredential
|
||||
func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) {
|
||||
had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{})
|
||||
func DeleteCredential(ctx context.Context, uid, id int64) (bool, error) {
|
||||
had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", uid).Delete(&WebAuthnCredential{})
|
||||
return had > 0, err
|
||||
}
|
||||
@@ -16,11 +16,15 @@ import (
|
||||
func TestGetWebAuthnCredentialByID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 1)
|
||||
res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "WebAuthn credential", res.Name)
|
||||
|
||||
_, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 342432)
|
||||
_, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 99999, 1)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
|
||||
|
||||
_, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 99999)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user