aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKamil DomaƄski <kamil@domanski.co>2021-11-08 23:47:19 +0100
committerGitHub <noreply@github.com>2021-11-08 23:47:19 +0100
commit021df29623bb0155b5a2ccad0e5f90fb348c8f4e (patch)
treec720bc34bd29620028c51d35c6d98044af89101e
parenta3f9e9234cbb099b821a6ea9c575927be18948de (diff)
downloadgitea-021df29623bb0155b5a2ccad0e5f90fb348c8f4e.tar.gz
gitea-021df29623bb0155b5a2ccad0e5f90fb348c8f4e.zip
Allow U2F 2FA without TOTP (#11573)
This change enables the usage of U2F without being forced to enroll an TOTP authenticator. The `/user/auth/u2f` has been changed to hide the "use TOTP instead" bar if TOTP is not enrolled. Fixes #5410 Fixes #17495
-rw-r--r--models/fixtures/u2f_registration.yml2
-rw-r--r--models/fixtures/user.yml16
-rw-r--r--models/login/twofactor.go6
-rw-r--r--models/login/u2f.go5
-rw-r--r--models/login/u2f_test.go2
-rw-r--r--models/user_test.go4
-rw-r--r--options/locale/locale_en-US.ini1
-rw-r--r--routers/web/user/auth.go45
-rw-r--r--routers/web/user/setting/security.go24
-rw-r--r--templates/user/auth/u2f.tmpl8
-rw-r--r--templates/user/settings/security_twofa.tmpl2
-rw-r--r--templates/user/settings/security_u2f.tmpl42
12 files changed, 100 insertions, 57 deletions
diff --git a/models/fixtures/u2f_registration.yml b/models/fixtures/u2f_registration.yml
index 4a9d1d9624..60555c43f1 100644
--- a/models/fixtures/u2f_registration.yml
+++ b/models/fixtures/u2f_registration.yml
@@ -1,7 +1,7 @@
-
id: 1
name: "U2F Key"
- user_id: 1
+ user_id: 32
counter: 0
created_unix: 946684800
updated_unix: 946684800
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index c49fe1b656..cf07542eed 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -542,3 +542,19 @@
avatar_email: user31@example.com
num_repos: 0
is_active: true
+
+-
+ id: 32
+ lower_name: user32
+ name: user32
+ full_name: User 32 (U2F test)
+ email: user32@example.com
+ passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
+ type: 0 # individual
+ salt: ZogKvWdyEx
+ is_admin: false
+ is_restricted: false
+ avatar: avatar32
+ avatar_email: user30@example.com
+ num_repos: 0
+ is_active: true
diff --git a/models/login/twofactor.go b/models/login/twofactor.go
index 1c4d2734fc..acb5e1b2d5 100644
--- a/models/login/twofactor.go
+++ b/models/login/twofactor.go
@@ -136,6 +136,12 @@ func GetTwoFactorByUID(uid int64) (*TwoFactor, error) {
return twofa, nil
}
+// HasTwoFactorByUID returns the two-factor authentication token associated with
+// the user, if any.
+func HasTwoFactorByUID(uid int64) (bool, error) {
+ return db.GetEngine(db.DefaultContext).Where("uid=?", uid).Exist(&TwoFactor{})
+}
+
// DeleteTwoFactorByID deletes two-factor authentication token by given ID.
func DeleteTwoFactorByID(id, userID int64) error {
cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&TwoFactor{
diff --git a/models/login/u2f.go b/models/login/u2f.go
index 05d39cc05e..8cea98463f 100644
--- a/models/login/u2f.go
+++ b/models/login/u2f.go
@@ -115,6 +115,11 @@ func GetU2FRegistrationsByUID(uid int64) (U2FRegistrationList, error) {
return getU2FRegistrationsByUID(db.GetEngine(db.DefaultContext), uid)
}
+// HasU2FRegistrationsByUID returns whether a given user has U2F registrations
+func HasU2FRegistrationsByUID(uid int64) (bool, error) {
+ return db.GetEngine(db.DefaultContext).Where("user_id = ?", uid).Exist(&U2FRegistration{})
+}
+
func createRegistration(e db.Engine, userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) {
raw, err := reg.MarshalBinary()
if err != nil {
diff --git a/models/login/u2f_test.go b/models/login/u2f_test.go
index 32505b62a6..8f5cea6150 100644
--- a/models/login/u2f_test.go
+++ b/models/login/u2f_test.go
@@ -29,7 +29,7 @@ func TestGetU2FRegistrationByID(t *testing.T) {
func TestGetU2FRegistrationsByUID(t *testing.T) {
assert.NoError(t, db.PrepareTestDatabase())
- res, err := GetU2FRegistrationsByUID(1)
+ res, err := GetU2FRegistrationsByUID(32)
assert.NoError(t, err)
assert.Len(t, res, 1)
diff --git a/models/user_test.go b/models/user_test.go
index 2dcca20346..3f3536dafa 100644
--- a/models/user_test.go
+++ b/models/user_test.go
@@ -147,13 +147,13 @@ func TestSearchUsers(t *testing.T) {
}
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
- []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30})
+ []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32})
testUserSuccess(&SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse},
[]int64{9})
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
- []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29, 30})
+ []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29, 30, 32})
testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 4a9e3c3894..2632531e2f 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -714,7 +714,6 @@ twofa_enrolled = Your account has been enrolled into two-factor authentication.
twofa_failed_get_secret = Failed to get secret.
u2f_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://fidoalliance.org/">FIDO U2F</a> standard.
-u2f_require_twofa = Your account must be enrolled in two-factor authentication to use security keys.
u2f_register_key = Add Security Key
u2f_nickname = Nickname
u2f_press_button = Press the button on your security key to register it.
diff --git a/routers/web/user/auth.go b/routers/web/user/auth.go
index 21d48e9834..55f304a7cb 100644
--- a/routers/web/user/auth.go
+++ b/routers/web/user/auth.go
@@ -211,38 +211,58 @@ func SignInPost(ctx *context.Context) {
return
}
- // If this user is enrolled in 2FA, we can't sign the user in just yet.
+ // If this user is enrolled in 2FA TOTP, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page.
- _, err = login.GetTwoFactorByUID(u.ID)
+ hasTOTPtwofa, err := login.HasTwoFactorByUID(u.ID)
if err != nil {
- if login.IsErrTwoFactorNotEnrolled(err) {
- handleSignIn(ctx, u, form.Remember)
- } else {
- ctx.ServerError("UserSignIn", err)
- }
+ ctx.ServerError("UserSignIn", err)
return
}
- // User needs to use 2FA, save data and redirect to 2FA page.
+ // Check if the user has u2f registration
+ hasU2Ftwofa, err := login.HasU2FRegistrationsByUID(u.ID)
+ if err != nil {
+ ctx.ServerError("UserSignIn", err)
+ return
+ }
+
+ if !hasTOTPtwofa && !hasU2Ftwofa {
+ // No two factor auth configured we can sign in the user
+ handleSignIn(ctx, u, form.Remember)
+ return
+ }
+
+ // User will need to use 2FA TOTP or U2F, save data
if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
ctx.ServerError("UserSignIn: Unable to set twofaUid in session", err)
return
}
+
if err := ctx.Session.Set("twofaRemember", form.Remember); err != nil {
ctx.ServerError("UserSignIn: Unable to set twofaRemember in session", err)
return
}
+
+ if hasTOTPtwofa {
+ // User will need to use U2F, save data
+ if err := ctx.Session.Set("totpEnrolled", u.ID); err != nil {
+ ctx.ServerError("UserSignIn: Unable to set u2fEnrolled in session", err)
+ return
+ }
+ }
+
if err := ctx.Session.Release(); err != nil {
ctx.ServerError("UserSignIn: Unable to save session", err)
return
}
- regs, err := login.GetU2FRegistrationsByUID(u.ID)
- if err == nil && len(regs) > 0 {
+ // If we have U2F redirect there first
+ if hasU2Ftwofa {
ctx.Redirect(setting.AppSubURL + "/user/u2f")
return
}
+ // Fallback to 2FA
ctx.Redirect(setting.AppSubURL + "/user/two_factor")
}
@@ -406,6 +426,11 @@ func U2F(ctx *context.Context) {
return
}
+ // See whether TOTP is also available.
+ if ctx.Session.Get("totpEnrolled") != nil {
+ ctx.Data["TOTPEnrolled"] = true
+ }
+
ctx.HTML(http.StatusOK, tplU2F)
}
diff --git a/routers/web/user/setting/security.go b/routers/web/user/setting/security.go
index 53f672282d..65e9790d47 100644
--- a/routers/web/user/setting/security.go
+++ b/routers/web/user/setting/security.go
@@ -55,23 +55,17 @@ func DeleteAccountLink(ctx *context.Context) {
}
func loadSecurityData(ctx *context.Context) {
- enrolled := true
- _, err := login.GetTwoFactorByUID(ctx.User.ID)
+ enrolled, err := login.HasTwoFactorByUID(ctx.User.ID)
if err != nil {
- if login.IsErrTwoFactorNotEnrolled(err) {
- enrolled = false
- } else {
- ctx.ServerError("SettingsTwoFactor", err)
- return
- }
+ ctx.ServerError("SettingsTwoFactor", err)
+ return
}
- ctx.Data["TwofaEnrolled"] = enrolled
- if enrolled {
- ctx.Data["U2FRegistrations"], err = login.GetU2FRegistrationsByUID(ctx.User.ID)
- if err != nil {
- ctx.ServerError("GetU2FRegistrationsByUID", err)
- return
- }
+ ctx.Data["TOTPEnrolled"] = enrolled
+
+ ctx.Data["U2FRegistrations"], err = login.GetU2FRegistrationsByUID(ctx.User.ID)
+ if err != nil {
+ ctx.ServerError("GetU2FRegistrationsByUID", err)
+ return
}
tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{UserID: ctx.User.ID})
diff --git a/templates/user/auth/u2f.tmpl b/templates/user/auth/u2f.tmpl
index 2013d14937..8b04866bbc 100644
--- a/templates/user/auth/u2f.tmpl
+++ b/templates/user/auth/u2f.tmpl
@@ -12,9 +12,11 @@
<p>{{.i18n.Tr "u2f_sign_in"}}</p>
</div>
<div id="wait-for-key" class="ui attached segment"><div class="ui active indeterminate inline loader"></div> {{.i18n.Tr "u2f_press_button"}} </div>
- <div class="ui attached segment">
- <a href="{{AppSubUrl}}/user/two_factor">{{.i18n.Tr "u2f_use_twofa"}}</a>
- </div>
+ {{if .TOTPEnrolled}}
+ <div class="ui attached segment">
+ <a href="{{AppSubUrl}}/user/two_factor">{{.i18n.Tr "u2f_use_twofa"}}</a>
+ </div>
+ {{end}}
</div>
</div>
</div>
diff --git a/templates/user/settings/security_twofa.tmpl b/templates/user/settings/security_twofa.tmpl
index f48b2f4cb2..3d6804d9c6 100644
--- a/templates/user/settings/security_twofa.tmpl
+++ b/templates/user/settings/security_twofa.tmpl
@@ -3,7 +3,7 @@
</h4>
<div class="ui attached segment">
<p>{{.i18n.Tr "settings.twofa_desc"}}</p>
- {{if .TwofaEnrolled}}
+ {{if .TOTPEnrolled}}
<p>{{$.i18n.Tr "settings.twofa_is_enrolled" | Str2html }}</p>
<form class="ui form" action="{{AppSubUrl}}/user/settings/security/two_factor/regenerate_scratch" method="post" enctype="multipart/form-data">
{{.CsrfTokenHtml}}
diff --git a/templates/user/settings/security_u2f.tmpl b/templates/user/settings/security_u2f.tmpl
index 8fe01d8c70..a2ff6c2212 100644
--- a/templates/user/settings/security_u2f.tmpl
+++ b/templates/user/settings/security_u2f.tmpl
@@ -3,32 +3,28 @@
</h4>
<div class="ui attached segment">
<p>{{.i18n.Tr "settings.u2f_desc" | Str2html}}</p>
- {{if .TwofaEnrolled}}
- <div class="ui key list">
- {{range .U2FRegistrations}}
- <div class="item">
- <div class="right floated content">
- <button class="ui red tiny button delete-button" data-modal-id="delete-registration" data-url="{{$.Link}}/u2f/delete" data-id="{{.ID}}">
- {{$.i18n.Tr "settings.delete_key"}}
- </button>
- </div>
- <div class="content">
- <strong>{{.Name}}</strong>
- </div>
+ <div class="ui key list">
+ {{range .U2FRegistrations}}
+ <div class="item">
+ <div class="right floated content">
+ <button class="ui red tiny button delete-button" id="delete-registration" data-url="{{$.Link}}/u2f/delete" data-id="{{.ID}}">
+ {{$.i18n.Tr "settings.delete_key"}}
+ </button>
+ </div>
+ <div class="content">
+ <strong>{{.Name}}</strong>
</div>
- {{end}}
- </div>
- <div class="ui form">
- {{.CsrfTokenHtml}}
- <div class="required field">
- <label for="nickname">{{.i18n.Tr "settings.u2f_nickname"}}</label>
- <input id="nickname" name="nickname" type="text" required>
</div>
- <button id="register-security-key" class="ui green button">{{svg "octicon-key"}} {{.i18n.Tr "settings.u2f_register_key"}}</button>
+ {{end}}
+ </div>
+ <div class="ui form">
+ {{.CsrfTokenHtml}}
+ <div class="required field">
+ <label for="nickname">{{.i18n.Tr "settings.u2f_nickname"}}</label>
+ <input id="nickname" name="nickname" type="text" required>
</div>
- {{else}}
- <b>{{.i18n.Tr "settings.u2f_require_twofa"}}</b>
- {{end}}
+ <button id="register-security-key" class="ui green button">{{svg "octicon-key"}} {{.i18n.Tr "settings.u2f_register_key"}}</button>
+ </div>
</div>
<div class="ui small modal" id="register-device">