From 571d752711dde137c72727295ed994334f696662 Mon Sep 17 00:00:00 2001 From: hiifong Date: Sat, 23 Nov 2024 16:39:53 +0800 Subject: [PATCH 01/12] Fix #32595 --- go.mod | 2 ++ go.sum | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index bbd81868684f8..28e25f95acfa7 100644 --- a/go.mod +++ b/go.mod @@ -336,6 +336,8 @@ replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-tra // TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2 +replace github.com/go-webauthn/webauthn => github.com/go-webauthn/webauthn v0.10.2 + exclude github.com/gofrs/uuid v3.2.0+incompatible exclude github.com/gofrs/uuid v4.0.0+incompatible diff --git a/go.sum b/go.sum index df3b7d899cf9f..49a10b35e2e2f 100644 --- a/go.sum +++ b/go.sum @@ -374,8 +374,8 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-testfixtures/testfixtures/v3 v3.11.0 h1:XxQr8AnPORcZkyNd7go5UNLPD3dULN8ixYISlzrlfEQ= github.com/go-testfixtures/testfixtures/v3 v3.11.0/go.mod h1:THmudHF1Ixq++J2/UodcJpxUphfyEd77m83TvDtryqE= -github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= -github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0= +github.com/go-webauthn/webauthn v0.10.2 h1:OG7B+DyuTytrEPFmTX503K77fqs3HDK/0Iv+z8UYbq4= +github.com/go-webauthn/webauthn v0.10.2/go.mod h1:Gd1IDsGAybuvK1NkwUTLbGmeksxuRJjVN2PE/xsPxHs= github.com/go-webauthn/x v0.1.15 h1:eG1OhggBJTkDE8gUeOlGRbRe8E/PSVG26YG4AyFbwkU= github.com/go-webauthn/x v0.1.15/go.mod h1:pf7VI23raFLHPO9VVIs9/u1etqwAOP0S2KoHGL6WbZ8= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= From bff0317ebb5fe007ec016a8edb55a78869c6798e Mon Sep 17 00:00:00 2001 From: hiifong Date: Sun, 24 Nov 2024 00:37:03 +0800 Subject: [PATCH 02/12] Set the default value of BackupEligible to true --- go.mod | 2 -- go.sum | 4 ++-- models/auth/webauthn.go | 3 +++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 28e25f95acfa7..bbd81868684f8 100644 --- a/go.mod +++ b/go.mod @@ -336,8 +336,6 @@ replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-tra // TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2 -replace github.com/go-webauthn/webauthn => github.com/go-webauthn/webauthn v0.10.2 - exclude github.com/gofrs/uuid v3.2.0+incompatible exclude github.com/gofrs/uuid v4.0.0+incompatible diff --git a/go.sum b/go.sum index 49a10b35e2e2f..df3b7d899cf9f 100644 --- a/go.sum +++ b/go.sum @@ -374,8 +374,8 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-testfixtures/testfixtures/v3 v3.11.0 h1:XxQr8AnPORcZkyNd7go5UNLPD3dULN8ixYISlzrlfEQ= github.com/go-testfixtures/testfixtures/v3 v3.11.0/go.mod h1:THmudHF1Ixq++J2/UodcJpxUphfyEd77m83TvDtryqE= -github.com/go-webauthn/webauthn v0.10.2 h1:OG7B+DyuTytrEPFmTX503K77fqs3HDK/0Iv+z8UYbq4= -github.com/go-webauthn/webauthn v0.10.2/go.mod h1:Gd1IDsGAybuvK1NkwUTLbGmeksxuRJjVN2PE/xsPxHs= +github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= +github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0= github.com/go-webauthn/x v0.1.15 h1:eG1OhggBJTkDE8gUeOlGRbRe8E/PSVG26YG4AyFbwkU= github.com/go-webauthn/x v0.1.15/go.mod h1:pf7VI23raFLHPO9VVIs9/u1etqwAOP0S2KoHGL6WbZ8= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index 553130ee2e9ee..95ec1a88d1c59 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -102,6 +102,9 @@ func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { SignCount: cred.SignCount, CloneWarning: cred.CloneWarning, }, + Flags: webauthn.CredentialFlags{ + BackupEligible: true, + }, }) } return creds From e670ec4fd69aaa801724f093833f9d97ce10845f Mon Sep 17 00:00:00 2001 From: hiifong Date: Sun, 24 Nov 2024 12:59:08 +0800 Subject: [PATCH 03/12] store flags to database --- models/auth/webauthn.go | 21 ++++++++++++++++++++- models/migrations/migrations.go | 1 + models/migrations/v1_23/v310.go | 20 ++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 models/migrations/v1_23/v310.go diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index 95ec1a88d1c59..def420ccfca2b 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" ) @@ -50,6 +51,7 @@ type WebAuthnCredential struct { PublicKey []byte AttestationType string AAGUID []byte + Flags protocol.AuthenticatorFlags SignCount uint32 `xorm:"BIGINT"` CloneWarning bool CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` @@ -103,7 +105,10 @@ func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { CloneWarning: cred.CloneWarning, }, Flags: webauthn.CredentialFlags{ - BackupEligible: true, + UserPresent: cred.Flags.HasUserPresent(), + UserVerified: cred.Flags.HasUserVerified(), + BackupEligible: cred.Flags.HasBackupEligible(), + BackupState: cred.Flags.HasBackupState(), }, }) } @@ -161,6 +166,19 @@ func GetWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []b // CreateCredential will create a new WebAuthnCredential from the given Credential func CreateCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) { + var flags protocol.AuthenticatorFlags + if cred.Flags.UserPresent { + flags |= protocol.FlagUserPresent + } + if cred.Flags.UserVerified { + flags |= protocol.FlagUserVerified + } + if cred.Flags.BackupEligible { + flags |= protocol.FlagBackupEligible + } + if cred.Flags.BackupState { + flags |= protocol.FlagBackupState + } c := &WebAuthnCredential{ UserID: userID, Name: name, @@ -168,6 +186,7 @@ func CreateCredential(ctx context.Context, userID int64, name string, cred *weba PublicKey: cred.PublicKey, AttestationType: cred.AttestationType, AAGUID: cred.Authenticator.AAGUID, + Flags: flags, SignCount: cred.Authenticator.SignCount, CloneWarning: false, } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index e0361af86ba8e..091d59284a7f4 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -367,6 +367,7 @@ func prepareMigrationTasks() []*migration { newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate), newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard), newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices), + newMigration(310, "Add flags on table webauthn_credential", v1_23.AddFlagsOnWebAuthnCredential), } return preparedMigrations } diff --git a/models/migrations/v1_23/v310.go b/models/migrations/v1_23/v310.go new file mode 100644 index 0000000000000..0c16e901f84b9 --- /dev/null +++ b/models/migrations/v1_23/v310.go @@ -0,0 +1,20 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_23 //nolint + +import ( + "github.com/go-webauthn/webauthn/protocol" + "xorm.io/xorm" +) + +func AddFlagsOnWebAuthnCredential(x *xorm.Engine) error { + type WebAuthnCredential struct { + Flags protocol.AuthenticatorFlags + } + if err := x.Sync(new(WebAuthnCredential)); err != nil { + return err + } + _, err := x.Exec("UPDATE webauthn_credential SET flags = 29") + return err +} From 62ddc5cefdea4f5d4564a82822bc14dcc68d2d28 Mon Sep 17 00:00:00 2001 From: hiifong Date: Mon, 25 Nov 2024 00:22:49 +0800 Subject: [PATCH 04/12] fix migration --- models/migrations/v1_23/v310.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/models/migrations/v1_23/v310.go b/models/migrations/v1_23/v310.go index 0c16e901f84b9..7bce2a9916867 100644 --- a/models/migrations/v1_23/v310.go +++ b/models/migrations/v1_23/v310.go @@ -8,13 +8,18 @@ import ( "xorm.io/xorm" ) +type WebAuthnCredential struct { + Flags protocol.AuthenticatorFlags +} + +func (cred WebAuthnCredential) TableName() string { + return "webauthn_credential" +} + func AddFlagsOnWebAuthnCredential(x *xorm.Engine) error { - type WebAuthnCredential struct { - Flags protocol.AuthenticatorFlags - } if err := x.Sync(new(WebAuthnCredential)); err != nil { return err } - _, err := x.Exec("UPDATE webauthn_credential SET flags = 29") + _, err := x.Exec("UPDATE webauthn_credential SET flags = 29 WHERE id > 0") return err } From 573d8123028b1cf7d640687592d7847698513eb6 Mon Sep 17 00:00:00 2001 From: hiifong Date: Mon, 25 Nov 2024 00:35:06 +0800 Subject: [PATCH 05/12] fix lint --- models/auth/webauthn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index def420ccfca2b..fffe1178a22ba 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -11,8 +11,8 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" - "github.com/go-webauthn/webauthn/protocol" + "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" ) From eed7be19c5f132fec25062b8d81177ab79c2d802 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 25 Nov 2024 22:45:42 +0800 Subject: [PATCH 06/12] use json --- models/auth/webauthn.go | 37 ++++++++++++++------------------- models/migrations/v1_23/v310.go | 16 +++++++++++--- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index fffe1178a22ba..e581545407f02 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -9,10 +9,11 @@ import ( "strings" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" - "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" ) @@ -51,7 +52,7 @@ type WebAuthnCredential struct { PublicKey []byte AttestationType string AAGUID []byte - Flags protocol.AuthenticatorFlags + CredentialFlags string `xorm:"TEXT DEFAULT ''"` SignCount uint32 `xorm:"BIGINT"` CloneWarning bool CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` @@ -95,6 +96,14 @@ type WebAuthnCredentialList []*WebAuthnCredential func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { creds := make([]webauthn.Credential, 0, len(list)) for _, cred := range list { + var flags webauthn.CredentialFlags + if cred.CredentialFlags != "" { + err := json.Unmarshal([]byte(cred.CredentialFlags), &flags) + if err != nil { + log.Error("Failed to unmarshal CredentialFlags, webauthn credential id:%d, err:%v", cred.ID, err) + continue + } + } creds = append(creds, webauthn.Credential{ ID: cred.CredentialID, PublicKey: cred.PublicKey, @@ -104,12 +113,7 @@ func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { SignCount: cred.SignCount, CloneWarning: cred.CloneWarning, }, - Flags: webauthn.CredentialFlags{ - UserPresent: cred.Flags.HasUserPresent(), - UserVerified: cred.Flags.HasUserVerified(), - BackupEligible: cred.Flags.HasBackupEligible(), - BackupState: cred.Flags.HasBackupState(), - }, + Flags: flags, }) } return creds @@ -166,18 +170,9 @@ func GetWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []b // CreateCredential will create a new WebAuthnCredential from the given Credential func CreateCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) { - var flags protocol.AuthenticatorFlags - if cred.Flags.UserPresent { - flags |= protocol.FlagUserPresent - } - if cred.Flags.UserVerified { - flags |= protocol.FlagUserVerified - } - if cred.Flags.BackupEligible { - flags |= protocol.FlagBackupEligible - } - if cred.Flags.BackupState { - flags |= protocol.FlagBackupState + flagsJSON, err := json.Marshal(cred.Flags) + if err != nil { + return nil, err } c := &WebAuthnCredential{ UserID: userID, @@ -186,7 +181,7 @@ func CreateCredential(ctx context.Context, userID int64, name string, cred *weba PublicKey: cred.PublicKey, AttestationType: cred.AttestationType, AAGUID: cred.Authenticator.AAGUID, - Flags: flags, + CredentialFlags: string(flagsJSON), SignCount: cred.Authenticator.SignCount, CloneWarning: false, } diff --git a/models/migrations/v1_23/v310.go b/models/migrations/v1_23/v310.go index 7bce2a9916867..ec5eb0f065294 100644 --- a/models/migrations/v1_23/v310.go +++ b/models/migrations/v1_23/v310.go @@ -4,12 +4,14 @@ package v1_23 //nolint import ( - "github.com/go-webauthn/webauthn/protocol" + "code.gitea.io/gitea/modules/json" + + "github.com/go-webauthn/webauthn/webauthn" "xorm.io/xorm" ) type WebAuthnCredential struct { - Flags protocol.AuthenticatorFlags + CredentialFlags string `xorm:"TEXT DEFAULT ''"` } func (cred WebAuthnCredential) TableName() string { @@ -20,6 +22,14 @@ func AddFlagsOnWebAuthnCredential(x *xorm.Engine) error { if err := x.Sync(new(WebAuthnCredential)); err != nil { return err } - _, err := x.Exec("UPDATE webauthn_credential SET flags = 29 WHERE id > 0") + + defaultCredentialFlags := webauthn.CredentialFlags{ + BackupEligible: true, + } + defaultCredentialFlagsJSON, err := json.Marshal(defaultCredentialFlags) + if err != nil { + return err + } + _, err = x.Exec("UPDATE webauthn_credential SET credential_flags = ? WHERE credential_flags = '' OR credential_flags IS NULL", string(defaultCredentialFlagsJSON)) return err } From 6ca9a67c971c683daba1c0ee7fd124175572b491 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 25 Nov 2024 22:53:58 +0800 Subject: [PATCH 07/12] add tests --- models/auth/webauthn.go | 10 ---------- models/auth/webauthn_test.go | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index e581545407f02..a3ebae800fb05 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -197,13 +197,3 @@ func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) { had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{}) return had > 0, err } - -// WebAuthnCredentials implements the webauthn.User interface -func WebAuthnCredentials(ctx context.Context, userID int64) ([]webauthn.Credential, error) { - dbCreds, err := GetWebAuthnCredentialsByUID(ctx, userID) - if err != nil { - return nil, err - } - - return dbCreds.ToCredentials(), nil -} diff --git a/models/auth/webauthn_test.go b/models/auth/webauthn_test.go index f1cf398adf558..bd0df9665e187 100644 --- a/models/auth/webauthn_test.go +++ b/models/auth/webauthn_test.go @@ -58,10 +58,24 @@ func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) { func TestCreateCredential(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - res, err := auth_model.CreateCredential(db.DefaultContext, 1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")}) + flags := webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: true, + BackupState: true, + } + res, err := auth_model.CreateCredential(db.DefaultContext, 1, "WebAuthn Created Credential", &webauthn.Credential{ + ID: []byte("Test"), + Flags: flags, + }) assert.NoError(t, err) assert.Equal(t, "WebAuthn Created Credential", res.Name) assert.Equal(t, []byte("Test"), res.CredentialID) - unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1}) + webauthnUser1 := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{UserID: 1}) + assert.Equal(t, "WebAuthn Created Credential", webauthnUser1.Name) + assert.Equal(t, []byte("Test"), webauthnUser1.CredentialID) + + credList := auth_model.WebAuthnCredentialList{webauthnUser1}.ToCredentials() + assert.Equal(t, flags, credList[0].Flags) } From 7925be3818bf71a2c4190fbd1d8a971f33c9b797 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 25 Nov 2024 23:09:39 +0800 Subject: [PATCH 08/12] add tests --- models/db/engine.go | 3 ++ models/migrations/base/tests.go | 8 ++--- models/migrations/v1_23/v310.go | 6 ++-- models/migrations/v1_23/v310_test.go | 53 ++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 models/migrations/v1_23/v310_test.go diff --git a/models/db/engine.go b/models/db/engine.go index e50a8580bf0b7..b17188945a458 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -134,6 +134,9 @@ func SyncAllTables() error { func InitEngine(ctx context.Context) error { xormEngine, err := newXORMEngine() if err != nil { + if strings.Contains(err.Error(), "SQLite3 support") { + return fmt.Errorf(`sqlite3 requires: -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err) + } return fmt.Errorf("failed to connect to database: %w", err) } diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go index ddf9a544daabf..c2134f702a51a 100644 --- a/models/migrations/base/tests.go +++ b/models/migrations/base/tests.go @@ -18,7 +18,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/testlogger" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "xorm.io/xorm" ) @@ -33,15 +33,15 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu ourSkip := 2 ourSkip += skip deferFn := testlogger.PrintCurrentTest(t, ourSkip) - assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) + require.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) if err := deleteDB(); err != nil { - t.Errorf("unable to reset database: %v", err) + t.Fatalf("unable to reset database: %v", err) return nil, deferFn } x, err := newXORMEngine() - assert.NoError(t, err) + require.NoError(t, err) if x != nil { oldDefer := deferFn deferFn = func() { diff --git a/models/migrations/v1_23/v310.go b/models/migrations/v1_23/v310.go index ec5eb0f065294..143884ee354e9 100644 --- a/models/migrations/v1_23/v310.go +++ b/models/migrations/v1_23/v310.go @@ -10,16 +10,16 @@ import ( "xorm.io/xorm" ) -type WebAuthnCredential struct { +type WebAuthnCredential310 struct { CredentialFlags string `xorm:"TEXT DEFAULT ''"` } -func (cred WebAuthnCredential) TableName() string { +func (cred WebAuthnCredential310) TableName() string { return "webauthn_credential" } func AddFlagsOnWebAuthnCredential(x *xorm.Engine) error { - if err := x.Sync(new(WebAuthnCredential)); err != nil { + if err := x.Sync(new(WebAuthnCredential310)); err != nil { return err } diff --git a/models/migrations/v1_23/v310_test.go b/models/migrations/v1_23/v310_test.go new file mode 100644 index 0000000000000..3bd8daca63664 --- /dev/null +++ b/models/migrations/v1_23/v310_test.go @@ -0,0 +1,53 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_23 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + + "github.com/stretchr/testify/require" +) + +type WebAuthnCredentialOld struct { + ID int64 `xorm:"pk autoincr"` +} + +func (cred WebAuthnCredentialOld) TableName() string { + return "webauthn_credential" +} + +func TestAddFlagsOnWebAuthnCredential(t *testing.T) { + x, deferable := base.PrepareTestEnv(t, 0, new(WebAuthnCredentialOld)) + defer deferable() + + _, err := x.Exec("INSERT INTO webauthn_credential (id) VALUES (1)") + require.NoError(t, err) + _, err = x.Exec("INSERT INTO webauthn_credential (id) VALUES (2)") + require.NoError(t, err) + + require.NoError(t, AddFlagsOnWebAuthnCredential(x)) + + getFlags := func() (s1, s2 string) { + x.Select("credential_flags").Table("webauthn_credential").Where("id=1").Get(&s1) + x.Select("credential_flags").Table("webauthn_credential").Where("id=2").Get(&s2) + return s1, s2 + } + + s1, s2 := getFlags() + require.Equal(t, `{"userPresent":false,"userVerified":false,"backupEligible":true,"backupState":false}`, s1) + require.Equal(t, `{"userPresent":false,"userVerified":false,"backupEligible":true,"backupState":false}`, s2) + + _, err = x.Table("webauthn_credential").Where("id=1").Update(map[string]any{"credential_flags": `{}`}) + require.NoError(t, err) + s1, s2 = getFlags() + require.Equal(t, `{}`, s1) + require.Equal(t, `{"userPresent":false,"userVerified":false,"backupEligible":true,"backupState":false}`, s2) + + require.NoError(t, AddFlagsOnWebAuthnCredential(x)) + s1, s2 = getFlags() + require.Equal(t, `{}`, s1) + require.Equal(t, `{"userPresent":false,"userVerified":false,"backupEligible":true,"backupState":false}`, s2) +} From d4d98ad58ffbea2f13c1bd0315d6c154ad04f6d0 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 25 Nov 2024 23:21:38 +0800 Subject: [PATCH 09/12] fix --- models/auth/webauthn.go | 2 +- models/migrations/v1_23/v310.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index a3ebae800fb05..cb2208bd2d083 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -52,7 +52,7 @@ type WebAuthnCredential struct { PublicKey []byte AttestationType string AAGUID []byte - CredentialFlags string `xorm:"TEXT DEFAULT ''"` + CredentialFlags string `xorm:"TEXT"` SignCount uint32 `xorm:"BIGINT"` CloneWarning bool CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` diff --git a/models/migrations/v1_23/v310.go b/models/migrations/v1_23/v310.go index 143884ee354e9..1350b413bae53 100644 --- a/models/migrations/v1_23/v310.go +++ b/models/migrations/v1_23/v310.go @@ -11,7 +11,7 @@ import ( ) type WebAuthnCredential310 struct { - CredentialFlags string `xorm:"TEXT DEFAULT ''"` + CredentialFlags string `xorm:"TEXT"` } func (cred WebAuthnCredential310) TableName() string { From c5a32f4fa462c6799a8ea5dedb5133e84e1eca1c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 26 Nov 2024 09:03:03 +0800 Subject: [PATCH 10/12] revert and fix --- models/auth/webauthn.go | 27 ++++------ models/auth/webauthn_test.go | 18 +------ models/migrations/migrations.go | 1 - models/migrations/v1_23/v310.go | 35 ------------ models/migrations/v1_23/v310_test.go | 53 ------------------- modules/auth/webauthn/webauthn.go | 41 +++++++------- routers/web/auth/webauthn.go | 8 +-- routers/web/user/setting/security/webauthn.go | 6 ++- web_src/js/features/user-auth-webauthn.ts | 22 ++++---- web_src/js/modules/fetch.ts | 4 +- web_src/js/types.ts | 2 +- 11 files changed, 56 insertions(+), 161 deletions(-) delete mode 100644 models/migrations/v1_23/v310.go delete mode 100644 models/migrations/v1_23/v310_test.go diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index cb2208bd2d083..553130ee2e9ee 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -9,8 +9,6 @@ import ( "strings" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -52,7 +50,6 @@ type WebAuthnCredential struct { PublicKey []byte AttestationType string AAGUID []byte - CredentialFlags string `xorm:"TEXT"` SignCount uint32 `xorm:"BIGINT"` CloneWarning bool CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` @@ -96,14 +93,6 @@ type WebAuthnCredentialList []*WebAuthnCredential func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { creds := make([]webauthn.Credential, 0, len(list)) for _, cred := range list { - var flags webauthn.CredentialFlags - if cred.CredentialFlags != "" { - err := json.Unmarshal([]byte(cred.CredentialFlags), &flags) - if err != nil { - log.Error("Failed to unmarshal CredentialFlags, webauthn credential id:%d, err:%v", cred.ID, err) - continue - } - } creds = append(creds, webauthn.Credential{ ID: cred.CredentialID, PublicKey: cred.PublicKey, @@ -113,7 +102,6 @@ func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { SignCount: cred.SignCount, CloneWarning: cred.CloneWarning, }, - Flags: flags, }) } return creds @@ -170,10 +158,6 @@ func GetWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []b // CreateCredential will create a new WebAuthnCredential from the given Credential func CreateCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) { - flagsJSON, err := json.Marshal(cred.Flags) - if err != nil { - return nil, err - } c := &WebAuthnCredential{ UserID: userID, Name: name, @@ -181,7 +165,6 @@ func CreateCredential(ctx context.Context, userID int64, name string, cred *weba PublicKey: cred.PublicKey, AttestationType: cred.AttestationType, AAGUID: cred.Authenticator.AAGUID, - CredentialFlags: string(flagsJSON), SignCount: cred.Authenticator.SignCount, CloneWarning: false, } @@ -197,3 +180,13 @@ func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) { had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{}) return had > 0, err } + +// WebAuthnCredentials implements the webauthn.User interface +func WebAuthnCredentials(ctx context.Context, userID int64) ([]webauthn.Credential, error) { + dbCreds, err := GetWebAuthnCredentialsByUID(ctx, userID) + if err != nil { + return nil, err + } + + return dbCreds.ToCredentials(), nil +} diff --git a/models/auth/webauthn_test.go b/models/auth/webauthn_test.go index bd0df9665e187..f1cf398adf558 100644 --- a/models/auth/webauthn_test.go +++ b/models/auth/webauthn_test.go @@ -58,24 +58,10 @@ func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) { func TestCreateCredential(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - flags := webauthn.CredentialFlags{ - UserPresent: true, - UserVerified: true, - BackupEligible: true, - BackupState: true, - } - res, err := auth_model.CreateCredential(db.DefaultContext, 1, "WebAuthn Created Credential", &webauthn.Credential{ - ID: []byte("Test"), - Flags: flags, - }) + res, err := auth_model.CreateCredential(db.DefaultContext, 1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")}) assert.NoError(t, err) assert.Equal(t, "WebAuthn Created Credential", res.Name) assert.Equal(t, []byte("Test"), res.CredentialID) - webauthnUser1 := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{UserID: 1}) - assert.Equal(t, "WebAuthn Created Credential", webauthnUser1.Name) - assert.Equal(t, []byte("Test"), webauthnUser1.CredentialID) - - credList := auth_model.WebAuthnCredentialList{webauthnUser1}.ToCredentials() - assert.Equal(t, flags, credList[0].Flags) + unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1}) } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 091d59284a7f4..e0361af86ba8e 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -367,7 +367,6 @@ func prepareMigrationTasks() []*migration { newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate), newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard), newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices), - newMigration(310, "Add flags on table webauthn_credential", v1_23.AddFlagsOnWebAuthnCredential), } return preparedMigrations } diff --git a/models/migrations/v1_23/v310.go b/models/migrations/v1_23/v310.go deleted file mode 100644 index 1350b413bae53..0000000000000 --- a/models/migrations/v1_23/v310.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package v1_23 //nolint - -import ( - "code.gitea.io/gitea/modules/json" - - "github.com/go-webauthn/webauthn/webauthn" - "xorm.io/xorm" -) - -type WebAuthnCredential310 struct { - CredentialFlags string `xorm:"TEXT"` -} - -func (cred WebAuthnCredential310) TableName() string { - return "webauthn_credential" -} - -func AddFlagsOnWebAuthnCredential(x *xorm.Engine) error { - if err := x.Sync(new(WebAuthnCredential310)); err != nil { - return err - } - - defaultCredentialFlags := webauthn.CredentialFlags{ - BackupEligible: true, - } - defaultCredentialFlagsJSON, err := json.Marshal(defaultCredentialFlags) - if err != nil { - return err - } - _, err = x.Exec("UPDATE webauthn_credential SET credential_flags = ? WHERE credential_flags = '' OR credential_flags IS NULL", string(defaultCredentialFlagsJSON)) - return err -} diff --git a/models/migrations/v1_23/v310_test.go b/models/migrations/v1_23/v310_test.go deleted file mode 100644 index 3bd8daca63664..0000000000000 --- a/models/migrations/v1_23/v310_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package v1_23 //nolint - -import ( - "testing" - - "code.gitea.io/gitea/models/migrations/base" - - "github.com/stretchr/testify/require" -) - -type WebAuthnCredentialOld struct { - ID int64 `xorm:"pk autoincr"` -} - -func (cred WebAuthnCredentialOld) TableName() string { - return "webauthn_credential" -} - -func TestAddFlagsOnWebAuthnCredential(t *testing.T) { - x, deferable := base.PrepareTestEnv(t, 0, new(WebAuthnCredentialOld)) - defer deferable() - - _, err := x.Exec("INSERT INTO webauthn_credential (id) VALUES (1)") - require.NoError(t, err) - _, err = x.Exec("INSERT INTO webauthn_credential (id) VALUES (2)") - require.NoError(t, err) - - require.NoError(t, AddFlagsOnWebAuthnCredential(x)) - - getFlags := func() (s1, s2 string) { - x.Select("credential_flags").Table("webauthn_credential").Where("id=1").Get(&s1) - x.Select("credential_flags").Table("webauthn_credential").Where("id=2").Get(&s2) - return s1, s2 - } - - s1, s2 := getFlags() - require.Equal(t, `{"userPresent":false,"userVerified":false,"backupEligible":true,"backupState":false}`, s1) - require.Equal(t, `{"userPresent":false,"userVerified":false,"backupEligible":true,"backupState":false}`, s2) - - _, err = x.Table("webauthn_credential").Where("id=1").Update(map[string]any{"credential_flags": `{}`}) - require.NoError(t, err) - s1, s2 = getFlags() - require.Equal(t, `{}`, s1) - require.Equal(t, `{"userPresent":false,"userVerified":false,"backupEligible":true,"backupState":false}`, s2) - - require.NoError(t, AddFlagsOnWebAuthnCredential(x)) - s1, s2 = getFlags() - require.Equal(t, `{}`, s1) - require.Equal(t, `{"userPresent":false,"userVerified":false,"backupEligible":true,"backupState":false}`, s2) -} diff --git a/modules/auth/webauthn/webauthn.go b/modules/auth/webauthn/webauthn.go index 790006ee567cf..e327de9d23c9d 100644 --- a/modules/auth/webauthn/webauthn.go +++ b/modules/auth/webauthn/webauthn.go @@ -4,13 +4,14 @@ package webauthn import ( + "context" "encoding/binary" "encoding/gob" "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" @@ -38,40 +39,40 @@ func Init() { } } -// User represents an implementation of webauthn.User based on User model -type User user_model.User +// user represents an implementation of webauthn.User based on User model +type user struct { + ctx context.Context + User *user_model.User +} + +var _ webauthn.User = (*user)(nil) + +func NewWebAuthnUser(ctx context.Context, u *user_model.User) webauthn.User { + return &user{ctx: ctx, User: u} +} // WebAuthnID implements the webauthn.User interface -func (u *User) WebAuthnID() []byte { +func (u *user) WebAuthnID() []byte { id := make([]byte, 8) - binary.PutVarint(id, u.ID) + binary.PutVarint(id, u.User.ID) return id } // WebAuthnName implements the webauthn.User interface -func (u *User) WebAuthnName() string { - if u.LoginName == "" { - return u.Name - } - return u.LoginName +func (u *user) WebAuthnName() string { + return util.IfZero(u.User.LoginName, u.User.Name) } // WebAuthnDisplayName implements the webauthn.User interface -func (u *User) WebAuthnDisplayName() string { - return (*user_model.User)(u).DisplayName() -} - -// WebAuthnIcon implements the webauthn.User interface -func (u *User) WebAuthnIcon() string { - return (*user_model.User)(u).AvatarLink(db.DefaultContext) +func (u *user) WebAuthnDisplayName() string { + return u.User.DisplayName() } // WebAuthnCredentials implements the webauthn.User interface -func (u *User) WebAuthnCredentials() []webauthn.Credential { - dbCreds, err := auth.GetWebAuthnCredentialsByUID(db.DefaultContext, u.ID) +func (u *user) WebAuthnCredentials() []webauthn.Credential { + dbCreds, err := auth.GetWebAuthnCredentialsByUID(u.ctx, u.User.ID) if err != nil { return nil } - return dbCreds.ToCredentials() } diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go index 3160c5e23f03c..4c106e261f43a 100644 --- a/routers/web/auth/webauthn.go +++ b/routers/web/auth/webauthn.go @@ -89,7 +89,7 @@ func WebAuthnPasskeyLogin(ctx *context.Context) { return nil, err } - return (*wa.User)(user), nil + return wa.NewWebAuthnUser(ctx, user), nil }, *sessionData, ctx.Req) if err != nil { // Failed authentication attempt. @@ -171,7 +171,8 @@ func WebAuthnLoginAssertion(ctx *context.Context) { return } - assertion, sessionData, err := wa.WebAuthn.BeginLogin((*wa.User)(user)) + webAuthnUser := wa.NewWebAuthnUser(ctx, user) + assertion, sessionData, err := wa.WebAuthn.BeginLogin(webAuthnUser) if err != nil { ctx.ServerError("webauthn.BeginLogin", err) return @@ -216,7 +217,8 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) { } // Validate the parsed response. - cred, err := wa.WebAuthn.ValidateLogin((*wa.User)(user), *sessionData, parsedResponse) + webAuthnUser := wa.NewWebAuthnUser(ctx, user) + cred, err := wa.WebAuthn.ValidateLogin(webAuthnUser, *sessionData, parsedResponse) if err != nil { // Failed authentication attempt. log.Info("Failed authentication attempt for %s from %s: %v", user.Name, ctx.RemoteAddr(), err) diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index aafc2f2a648ca..70bfaac6e0943 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -51,7 +51,8 @@ func WebAuthnRegister(ctx *context.Context) { return } - credentialOptions, sessionData, err := wa.WebAuthn.BeginRegistration((*wa.User)(ctx.Doer), webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{ + webAuthnUser := wa.NewWebAuthnUser(ctx, ctx.Doer) + credentialOptions, sessionData, err := wa.WebAuthn.BeginRegistration(webAuthnUser, webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{ ResidentKey: protocol.ResidentKeyRequirementRequired, })) if err != nil { @@ -92,7 +93,8 @@ func WebauthnRegisterPost(ctx *context.Context) { }() // Verify that the challenge succeeded - cred, err := wa.WebAuthn.FinishRegistration((*wa.User)(ctx.Doer), *sessionData, ctx.Req) + webAuthnUser := wa.NewWebAuthnUser(ctx, ctx.Doer) + cred, err := wa.WebAuthn.FinishRegistration(webAuthnUser, *sessionData, ctx.Req) if err != nil { if pErr, ok := err.(*protocol.Error); ok { log.Error("Unable to finish registration due to error: %v\nDevInfo: %s", pErr, pErr.DevInfo) diff --git a/web_src/js/features/user-auth-webauthn.ts b/web_src/js/features/user-auth-webauthn.ts index 610b559833dc7..70516c280d897 100644 --- a/web_src/js/features/user-auth-webauthn.ts +++ b/web_src/js/features/user-auth-webauthn.ts @@ -40,14 +40,15 @@ async function loginPasskey() { try { const credential = await navigator.credentials.get({ publicKey: options.publicKey, - }); + }) as PublicKeyCredential; + const credResp = credential.response as AuthenticatorAssertionResponse; // Move data into Arrays in case it is super long - const authData = new Uint8Array(credential.response.authenticatorData); - const clientDataJSON = new Uint8Array(credential.response.clientDataJSON); + const authData = new Uint8Array(credResp.authenticatorData); + const clientDataJSON = new Uint8Array(credResp.clientDataJSON); const rawId = new Uint8Array(credential.rawId); - const sig = new Uint8Array(credential.response.signature); - const userHandle = new Uint8Array(credential.response.userHandle); + const sig = new Uint8Array(credResp.signature); + const userHandle = new Uint8Array(credResp.userHandle); const res = await POST(`${appSubUrl}/user/webauthn/passkey/login`, { data: { @@ -175,7 +176,7 @@ async function webauthnRegistered(newCredential) { window.location.reload(); } -function webAuthnError(errorType, message) { +function webAuthnError(errorType: string, message:string = '') { const elErrorMsg = document.querySelector(`#webauthn-error-msg`); if (errorType === 'general') { @@ -207,10 +208,9 @@ function detectWebAuthnSupport() { } export function initUserAuthWebAuthnRegister() { - const elRegister = document.querySelector('#register-webauthn'); - if (!elRegister) { - return; - } + const elRegister = document.querySelector('#register-webauthn'); + if (!elRegister) return; + if (!detectWebAuthnSupport()) { elRegister.disabled = true; return; @@ -222,7 +222,7 @@ export function initUserAuthWebAuthnRegister() { } async function webAuthnRegisterRequest() { - const elNickname = document.querySelector('#nickname'); + const elNickname = document.querySelector('#nickname'); const formData = new FormData(); formData.append('name', elNickname.value); diff --git a/web_src/js/modules/fetch.ts b/web_src/js/modules/fetch.ts index de3ef1de7ed84..0c078bfe78323 100644 --- a/web_src/js/modules/fetch.ts +++ b/web_src/js/modules/fetch.ts @@ -1,5 +1,5 @@ import {isObject} from '../utils.ts'; -import type {RequestData, RequestOpts} from '../types.ts'; +import type {RequestOpts} from '../types.ts'; const {csrfToken} = window.config; @@ -10,7 +10,7 @@ const safeMethods = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']); // which will automatically set an appropriate headers. For json content, only object // and array types are currently supported. export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}): Promise { - let body: RequestData; + let body: string | FormData | URLSearchParams; let contentType: string; if (data instanceof FormData || data instanceof URLSearchParams) { body = data; diff --git a/web_src/js/types.ts b/web_src/js/types.ts index 317719fad5c95..29279bbded43c 100644 --- a/web_src/js/types.ts +++ b/web_src/js/types.ts @@ -24,7 +24,7 @@ export type Config = { export type Intent = 'error' | 'warning' | 'info'; -export type RequestData = string | FormData | URLSearchParams; +export type RequestData = string | FormData | URLSearchParams | Record; export type RequestOpts = { data?: RequestData, From 5eeac469dc9bf609f578bc306882d6cb22080f20 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 26 Nov 2024 09:17:40 +0800 Subject: [PATCH 11/12] provide default auth flags --- models/auth/webauthn.go | 22 +++++++++++++++++++++- modules/auth/webauthn/webauthn.go | 8 +++++--- routers/web/auth/webauthn.go | 2 +- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index 553130ee2e9ee..6d8b54295790e 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" ) @@ -89,14 +90,33 @@ func (cred *WebAuthnCredential) AfterLoad() { // WebAuthnCredentialList is a list of *WebAuthnCredential type WebAuthnCredentialList []*WebAuthnCredential +// newCredentialFlagsFromAuthenticatorFlags is copied from https://github.com/go-webauthn/webauthn/pull/337 +// to convert protocol.AuthenticatorFlags to webauthn.CredentialFlags +func newCredentialFlagsFromAuthenticatorFlags(flags protocol.AuthenticatorFlags) webauthn.CredentialFlags { + return webauthn.CredentialFlags{ + UserPresent: flags.HasUserPresent(), + UserVerified: flags.HasUserVerified(), + BackupEligible: flags.HasBackupEligible(), + BackupState: flags.HasBackupState(), + } +} + // ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials -func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { +func (list WebAuthnCredentialList) ToCredentials(defaultAuthFlags ...protocol.AuthenticatorFlags) []webauthn.Credential { + // TODO: at the moment, Gitea doesn't store or check the flags + // so we need to use the default flags from the authenticator to make the login validation pass + // In the future, we should: + // 1. store the flags when registering the credential + // 2. provide the stored flags when converting the credentials (for login) + // 3. for old users, still use this fallback to the default flags + defAuthFlags := util.OptionalArg(defaultAuthFlags) creds := make([]webauthn.Credential, 0, len(list)) for _, cred := range list { creds = append(creds, webauthn.Credential{ ID: cred.CredentialID, PublicKey: cred.PublicKey, AttestationType: cred.AttestationType, + Flags: newCredentialFlagsFromAuthenticatorFlags(defAuthFlags), Authenticator: webauthn.Authenticator{ AAGUID: cred.AAGUID, SignCount: cred.SignCount, diff --git a/modules/auth/webauthn/webauthn.go b/modules/auth/webauthn/webauthn.go index e327de9d23c9d..cbf5279c651fd 100644 --- a/modules/auth/webauthn/webauthn.go +++ b/modules/auth/webauthn/webauthn.go @@ -43,12 +43,14 @@ func Init() { type user struct { ctx context.Context User *user_model.User + + defaultAuthFlags protocol.AuthenticatorFlags } var _ webauthn.User = (*user)(nil) -func NewWebAuthnUser(ctx context.Context, u *user_model.User) webauthn.User { - return &user{ctx: ctx, User: u} +func NewWebAuthnUser(ctx context.Context, u *user_model.User, defaultAuthFlags ...protocol.AuthenticatorFlags) webauthn.User { + return &user{ctx: ctx, User: u, defaultAuthFlags: util.OptionalArg(defaultAuthFlags)} } // WebAuthnID implements the webauthn.User interface @@ -74,5 +76,5 @@ func (u *user) WebAuthnCredentials() []webauthn.Credential { if err != nil { return nil } - return dbCreds.ToCredentials() + return dbCreds.ToCredentials(u.defaultAuthFlags) } diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go index 4c106e261f43a..c00b4fcaf7f85 100644 --- a/routers/web/auth/webauthn.go +++ b/routers/web/auth/webauthn.go @@ -217,7 +217,7 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) { } // Validate the parsed response. - webAuthnUser := wa.NewWebAuthnUser(ctx, user) + webAuthnUser := wa.NewWebAuthnUser(ctx, user, parsedResponse.Response.AuthenticatorData.Flags) cred, err := wa.WebAuthn.ValidateLogin(webAuthnUser, *sessionData, parsedResponse) if err != nil { // Failed authentication attempt. From 555577e0e648c52a6f5eb03979110a8be57606e5 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 26 Nov 2024 22:59:07 +0800 Subject: [PATCH 12/12] fix --- routers/web/auth/webauthn.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go index c00b4fcaf7f85..ba25d4507052e 100644 --- a/routers/web/auth/webauthn.go +++ b/routers/web/auth/webauthn.go @@ -76,8 +76,17 @@ func WebAuthnPasskeyLogin(ctx *context.Context) { }() // Validate the parsed response. + + // ParseCredentialRequestResponse+ValidateDiscoverableLogin equals to FinishDiscoverableLogin, but we need to ParseCredentialRequestResponse first to get flags var user *user_model.User - cred, err := wa.WebAuthn.FinishDiscoverableLogin(func(rawID, userHandle []byte) (webauthn.User, error) { + parsedResponse, err := protocol.ParseCredentialRequestResponse(ctx.Req) + if err != nil { + // Failed authentication attempt. + log.Info("Failed authentication attempt for %s from %s: %v", user.Name, ctx.RemoteAddr(), err) + ctx.Status(http.StatusForbidden) + return + } + cred, err := wa.WebAuthn.ValidateDiscoverableLogin(func(rawID, userHandle []byte) (webauthn.User, error) { userID, n := binary.Varint(userHandle) if n <= 0 { return nil, errors.New("invalid rawID") @@ -89,8 +98,8 @@ func WebAuthnPasskeyLogin(ctx *context.Context) { return nil, err } - return wa.NewWebAuthnUser(ctx, user), nil - }, *sessionData, ctx.Req) + return wa.NewWebAuthnUser(ctx, user, parsedResponse.Response.AuthenticatorData.Flags), nil + }, *sessionData, parsedResponse) if err != nil { // Failed authentication attempt. log.Info("Failed authentication attempt for passkey from %s: %v", ctx.RemoteAddr(), err)