aboutsummaryrefslogtreecommitdiffstats
path: root/routers/user/setting.go
diff options
context:
space:
mode:
Diffstat (limited to 'routers/user/setting.go')
-rw-r--r--routers/user/setting.go194
1 files changed, 194 insertions, 0 deletions
diff --git a/routers/user/setting.go b/routers/user/setting.go
index bba0f1e876..8fc2be0ce4 100644
--- a/routers/user/setting.go
+++ b/routers/user/setting.go
@@ -5,12 +5,19 @@
package user
import (
+ "bytes"
"errors"
"fmt"
"io/ioutil"
"strings"
"github.com/Unknwon/com"
+ "github.com/pquerna/otp"
+ "github.com/pquerna/otp/totp"
+
+ "encoding/base64"
+ "html/template"
+ "image/png"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
@@ -28,6 +35,8 @@ const (
tplSettingsSSHKeys base.TplName = "user/settings/sshkeys"
tplSettingsSocial base.TplName = "user/settings/social"
tplSettingsApplications base.TplName = "user/settings/applications"
+ tplSettingsTwofa base.TplName = "user/settings/twofa"
+ tplSettingsTwofaEnroll base.TplName = "user/settings/twofa_enroll"
tplSettingsDelete base.TplName = "user/settings/delete"
tplSecurity base.TplName = "user/security"
)
@@ -437,6 +446,191 @@ func SettingsDeleteApplication(ctx *context.Context) {
})
}
+// SettingsTwoFactor renders the 2FA page.
+func SettingsTwoFactor(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("settings")
+ ctx.Data["PageIsSettingsTwofa"] = true
+
+ enrolled := true
+ _, err := models.GetTwoFactorByUID(ctx.User.ID)
+ if err != nil {
+ if models.IsErrTwoFactorNotEnrolled(err) {
+ enrolled = false
+ } else {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+ }
+
+ ctx.Data["TwofaEnrolled"] = enrolled
+ ctx.HTML(200, tplSettingsTwofa)
+}
+
+// SettingsTwoFactorRegenerateScratch regenerates the user's 2FA scratch code.
+func SettingsTwoFactorRegenerateScratch(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("settings")
+ ctx.Data["PageIsSettingsTwofa"] = true
+
+ t, err := models.GetTwoFactorByUID(ctx.User.ID)
+ if err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+
+ if err = t.GenerateScratchToken(); err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+
+ if err = models.UpdateTwoFactor(t); err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", t.ScratchToken))
+ ctx.Redirect(setting.AppSubURL + "/user/settings/two_factor")
+}
+
+// SettingsTwoFactorDisable deletes the user's 2FA settings.
+func SettingsTwoFactorDisable(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("settings")
+ ctx.Data["PageIsSettingsTwofa"] = true
+
+ t, err := models.GetTwoFactorByUID(ctx.User.ID)
+ if err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+
+ if err = models.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
+ ctx.Redirect(setting.AppSubURL + "/user/settings/two_factor")
+}
+
+func twofaGenerateSecretAndQr(ctx *context.Context) bool {
+ var otpKey *otp.Key
+ var err error
+ uri := ctx.Session.Get("twofaUri")
+ if uri != nil {
+ otpKey, err = otp.NewKeyFromURL(uri.(string))
+ }
+ if otpKey == nil {
+ err = nil // clear the error, in case the URL was invalid
+ otpKey, err = totp.Generate(totp.GenerateOpts{
+ Issuer: setting.AppName,
+ AccountName: ctx.User.Name,
+ })
+ if err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return false
+ }
+ }
+
+ ctx.Data["TwofaSecret"] = otpKey.Secret()
+ img, err := otpKey.Image(320, 240)
+ if err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return false
+ }
+
+ var imgBytes bytes.Buffer
+ if err = png.Encode(&imgBytes, img); err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return false
+ }
+
+ ctx.Data["QrUri"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes.Bytes()))
+ ctx.Session.Set("twofaSecret", otpKey.Secret())
+ ctx.Session.Set("twofaUri", otpKey.String())
+ return true
+}
+
+// SettingsTwoFactorEnroll shows the page where the user can enroll into 2FA.
+func SettingsTwoFactorEnroll(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("settings")
+ ctx.Data["PageIsSettingsTwofa"] = true
+
+ t, err := models.GetTwoFactorByUID(ctx.User.ID)
+ if t != nil {
+ // already enrolled
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+ if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+
+ if !twofaGenerateSecretAndQr(ctx) {
+ return
+ }
+
+ ctx.HTML(200, tplSettingsTwofaEnroll)
+}
+
+// SettingsTwoFactorEnrollPost handles enrolling the user into 2FA.
+func SettingsTwoFactorEnrollPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
+ ctx.Data["Title"] = ctx.Tr("settings")
+ ctx.Data["PageIsSettingsTwofa"] = true
+
+ t, err := models.GetTwoFactorByUID(ctx.User.ID)
+ if t != nil {
+ // already enrolled
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+ if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+
+ if ctx.HasError() {
+ if !twofaGenerateSecretAndQr(ctx) {
+ return
+ }
+ ctx.HTML(200, tplSettingsTwofaEnroll)
+ return
+ }
+
+ secret := ctx.Session.Get("twofaSecret").(string)
+ if !totp.Validate(form.Passcode, secret) {
+ if !twofaGenerateSecretAndQr(ctx) {
+ return
+ }
+ ctx.Flash.Error(ctx.Tr("settings.passcode_invalid"))
+ ctx.HTML(200, tplSettingsTwofaEnroll)
+ return
+ }
+
+ t = &models.TwoFactor{
+ UID: ctx.User.ID,
+ }
+ err = t.SetSecret(secret)
+ if err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+ err = t.GenerateScratchToken()
+ if err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+
+ if err = models.NewTwoFactor(t); err != nil {
+ ctx.Handle(500, "SettingsTwoFactor", err)
+ return
+ }
+
+ ctx.Session.Delete("twofaSecret")
+ ctx.Session.Delete("twofaUri")
+ ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", t.ScratchToken))
+ ctx.Redirect(setting.AppSubURL + "/user/settings/two_factor")
+}
+
// SettingsDelete render user suicide page and response for delete user himself
func SettingsDelete(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")