code.gitea.io/gitea@v1.21.7/routers/web/user/setting/security/2fa.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2018 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package security 6 7 import ( 8 "bytes" 9 "encoding/base64" 10 "html/template" 11 "image/png" 12 "net/http" 13 "strings" 14 15 "code.gitea.io/gitea/models/auth" 16 "code.gitea.io/gitea/modules/context" 17 "code.gitea.io/gitea/modules/log" 18 "code.gitea.io/gitea/modules/setting" 19 "code.gitea.io/gitea/modules/web" 20 "code.gitea.io/gitea/services/forms" 21 22 "github.com/pquerna/otp" 23 "github.com/pquerna/otp/totp" 24 ) 25 26 // RegenerateScratchTwoFactor regenerates the user's 2FA scratch code. 27 func RegenerateScratchTwoFactor(ctx *context.Context) { 28 ctx.Data["Title"] = ctx.Tr("settings") 29 ctx.Data["PageIsSettingsSecurity"] = true 30 31 t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID) 32 if err != nil { 33 if auth.IsErrTwoFactorNotEnrolled(err) { 34 ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled")) 35 ctx.Redirect(setting.AppSubURL + "/user/settings/security") 36 } 37 ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) 38 return 39 } 40 41 token, err := t.GenerateScratchToken() 42 if err != nil { 43 ctx.ServerError("SettingsTwoFactor: Failed to GenerateScratchToken", err) 44 return 45 } 46 47 if err = auth.UpdateTwoFactor(ctx, t); err != nil { 48 ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err) 49 return 50 } 51 52 ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", token)) 53 ctx.Redirect(setting.AppSubURL + "/user/settings/security") 54 } 55 56 // DisableTwoFactor deletes the user's 2FA settings. 57 func DisableTwoFactor(ctx *context.Context) { 58 ctx.Data["Title"] = ctx.Tr("settings") 59 ctx.Data["PageIsSettingsSecurity"] = true 60 61 t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID) 62 if err != nil { 63 if auth.IsErrTwoFactorNotEnrolled(err) { 64 ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled")) 65 ctx.Redirect(setting.AppSubURL + "/user/settings/security") 66 } 67 ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) 68 return 69 } 70 71 if err = auth.DeleteTwoFactorByID(ctx, t.ID, ctx.Doer.ID); err != nil { 72 if auth.IsErrTwoFactorNotEnrolled(err) { 73 // There is a potential DB race here - we must have been disabled by another request in the intervening period 74 ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) 75 ctx.Redirect(setting.AppSubURL + "/user/settings/security") 76 } 77 ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err) 78 return 79 } 80 81 ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) 82 ctx.Redirect(setting.AppSubURL + "/user/settings/security") 83 } 84 85 func twofaGenerateSecretAndQr(ctx *context.Context) bool { 86 var otpKey *otp.Key 87 var err error 88 uri := ctx.Session.Get("twofaUri") 89 if uri != nil { 90 otpKey, err = otp.NewKeyFromURL(uri.(string)) 91 if err != nil { 92 ctx.ServerError("SettingsTwoFactor: Failed NewKeyFromURL: ", err) 93 return false 94 } 95 } 96 // Filter unsafe character ':' in issuer 97 issuer := strings.ReplaceAll(setting.AppName+" ("+setting.Domain+")", ":", "") 98 if otpKey == nil { 99 otpKey, err = totp.Generate(totp.GenerateOpts{ 100 SecretSize: 40, 101 Issuer: issuer, 102 AccountName: ctx.Doer.Name, 103 }) 104 if err != nil { 105 ctx.ServerError("SettingsTwoFactor: totpGenerate Failed", err) 106 return false 107 } 108 } 109 110 ctx.Data["TwofaSecret"] = otpKey.Secret() 111 img, err := otpKey.Image(320, 240) 112 if err != nil { 113 ctx.ServerError("SettingsTwoFactor: otpKey image generation failed", err) 114 return false 115 } 116 117 var imgBytes bytes.Buffer 118 if err = png.Encode(&imgBytes, img); err != nil { 119 ctx.ServerError("SettingsTwoFactor: otpKey png encoding failed", err) 120 return false 121 } 122 123 ctx.Data["QrUri"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes.Bytes())) 124 125 if err := ctx.Session.Set("twofaSecret", otpKey.Secret()); err != nil { 126 ctx.ServerError("SettingsTwoFactor: Failed to set session for twofaSecret", err) 127 return false 128 } 129 130 if err := ctx.Session.Set("twofaUri", otpKey.String()); err != nil { 131 ctx.ServerError("SettingsTwoFactor: Failed to set session for twofaUri", err) 132 return false 133 } 134 135 // Here we're just going to try to release the session early 136 if err := ctx.Session.Release(); err != nil { 137 // we'll tolerate errors here as they *should* get saved elsewhere 138 log.Error("Unable to save changes to the session: %v", err) 139 } 140 return true 141 } 142 143 // EnrollTwoFactor shows the page where the user can enroll into 2FA. 144 func EnrollTwoFactor(ctx *context.Context) { 145 ctx.Data["Title"] = ctx.Tr("settings") 146 ctx.Data["PageIsSettingsSecurity"] = true 147 148 t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID) 149 if t != nil { 150 // already enrolled - we should redirect back! 151 log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.Doer) 152 ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled")) 153 ctx.Redirect(setting.AppSubURL + "/user/settings/security") 154 return 155 } 156 if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) { 157 ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err) 158 return 159 } 160 161 if !twofaGenerateSecretAndQr(ctx) { 162 return 163 } 164 165 ctx.HTML(http.StatusOK, tplSettingsTwofaEnroll) 166 } 167 168 // EnrollTwoFactorPost handles enrolling the user into 2FA. 169 func EnrollTwoFactorPost(ctx *context.Context) { 170 form := web.GetForm(ctx).(*forms.TwoFactorAuthForm) 171 ctx.Data["Title"] = ctx.Tr("settings") 172 ctx.Data["PageIsSettingsSecurity"] = true 173 174 t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID) 175 if t != nil { 176 // already enrolled 177 ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled")) 178 ctx.Redirect(setting.AppSubURL + "/user/settings/security") 179 return 180 } 181 if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) { 182 ctx.ServerError("SettingsTwoFactor: Failed to check if already enrolled with GetTwoFactorByUID", err) 183 return 184 } 185 186 if ctx.HasError() { 187 if !twofaGenerateSecretAndQr(ctx) { 188 return 189 } 190 ctx.HTML(http.StatusOK, tplSettingsTwofaEnroll) 191 return 192 } 193 194 secretRaw := ctx.Session.Get("twofaSecret") 195 if secretRaw == nil { 196 ctx.Flash.Error(ctx.Tr("settings.twofa_failed_get_secret")) 197 ctx.Redirect(setting.AppSubURL + "/user/settings/security/two_factor/enroll") 198 return 199 } 200 201 secret := secretRaw.(string) 202 if !totp.Validate(form.Passcode, secret) { 203 if !twofaGenerateSecretAndQr(ctx) { 204 return 205 } 206 ctx.Flash.Error(ctx.Tr("settings.passcode_invalid")) 207 ctx.Redirect(setting.AppSubURL + "/user/settings/security/two_factor/enroll") 208 return 209 } 210 211 t = &auth.TwoFactor{ 212 UID: ctx.Doer.ID, 213 } 214 err = t.SetSecret(secret) 215 if err != nil { 216 ctx.ServerError("SettingsTwoFactor: Failed to set secret", err) 217 return 218 } 219 token, err := t.GenerateScratchToken() 220 if err != nil { 221 ctx.ServerError("SettingsTwoFactor: Failed to generate scratch token", err) 222 return 223 } 224 225 // Now we have to delete the secrets - because if we fail to insert then it's highly likely that they have already been used 226 // If we can detect the unique constraint failure below we can move this to after the NewTwoFactor 227 if err := ctx.Session.Delete("twofaSecret"); err != nil { 228 // tolerate this failure - it's more important to continue 229 log.Error("Unable to delete twofaSecret from the session: Error: %v", err) 230 } 231 if err := ctx.Session.Delete("twofaUri"); err != nil { 232 // tolerate this failure - it's more important to continue 233 log.Error("Unable to delete twofaUri from the session: Error: %v", err) 234 } 235 if err := ctx.Session.Release(); err != nil { 236 // tolerate this failure - it's more important to continue 237 log.Error("Unable to save changes to the session: %v", err) 238 } 239 240 if err = auth.NewTwoFactor(ctx, t); err != nil { 241 // FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us. 242 // If there is a unique constraint fail we should just tolerate the error 243 ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err) 244 return 245 } 246 247 ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token)) 248 ctx.Redirect(setting.AppSubURL + "/user/settings/security") 249 }