mirror of
https://github.com/go-gitea/gitea
synced 2025-08-02 15:48:35 +00:00
Refactor auth package (#17962)
This commit is contained in:
250
routers/web/user/setting/security/2fa.go
Normal file
250
routers/web/user/setting/security/2fa.go
Normal file
@@ -0,0 +1,250 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"html/template"
|
||||
"image/png"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
// RegenerateScratchTwoFactor regenerates the user's 2FA scratch code.
|
||||
func RegenerateScratchTwoFactor(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
t, err := auth.GetTwoFactorByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := t.GenerateScratchToken()
|
||||
if err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to GenerateScratchToken", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = auth.UpdateTwoFactor(t); err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", token))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
|
||||
// DisableTwoFactor deletes the user's 2FA settings.
|
||||
func DisableTwoFactor(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
t, err := auth.GetTwoFactorByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = auth.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
|
||||
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
// There is a potential DB race here - we must have been disabled by another request in the intervening period
|
||||
ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
|
||||
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 err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed NewKeyFromURL: ", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Filter unsafe character ':' in issuer
|
||||
issuer := strings.ReplaceAll(setting.AppName+" ("+setting.Domain+")", ":", "")
|
||||
if otpKey == nil {
|
||||
otpKey, err = totp.Generate(totp.GenerateOpts{
|
||||
SecretSize: 40,
|
||||
Issuer: issuer,
|
||||
AccountName: ctx.User.Name,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: totpGenerate Failed", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["TwofaSecret"] = otpKey.Secret()
|
||||
img, err := otpKey.Image(320, 240)
|
||||
if err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: otpKey image generation failed", err)
|
||||
return false
|
||||
}
|
||||
|
||||
var imgBytes bytes.Buffer
|
||||
if err = png.Encode(&imgBytes, img); err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: otpKey png encoding failed", err)
|
||||
return false
|
||||
}
|
||||
|
||||
ctx.Data["QrUri"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes.Bytes()))
|
||||
|
||||
if err := ctx.Session.Set("twofaSecret", otpKey.Secret()); err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to set session for twofaSecret", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if err := ctx.Session.Set("twofaUri", otpKey.String()); err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to set session for twofaUri", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Here we're just going to try to release the session early
|
||||
if err := ctx.Session.Release(); err != nil {
|
||||
// we'll tolerate errors here as they *should* get saved elsewhere
|
||||
log.Error("Unable to save changes to the session: %v", err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// EnrollTwoFactor shows the page where the user can enroll into 2FA.
|
||||
func EnrollTwoFactor(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
t, err := auth.GetTwoFactorByUID(ctx.User.ID)
|
||||
if t != nil {
|
||||
// already enrolled - we should redirect back!
|
||||
log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.User)
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
return
|
||||
}
|
||||
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !twofaGenerateSecretAndQr(ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsTwofaEnroll)
|
||||
}
|
||||
|
||||
// EnrollTwoFactorPost handles enrolling the user into 2FA.
|
||||
func EnrollTwoFactorPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.TwoFactorAuthForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
t, err := auth.GetTwoFactorByUID(ctx.User.ID)
|
||||
if t != nil {
|
||||
// already enrolled
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
return
|
||||
}
|
||||
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to check if already enrolled with GetTwoFactorByUID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.HasError() {
|
||||
if !twofaGenerateSecretAndQr(ctx) {
|
||||
return
|
||||
}
|
||||
ctx.HTML(http.StatusOK, tplSettingsTwofaEnroll)
|
||||
return
|
||||
}
|
||||
|
||||
secretRaw := ctx.Session.Get("twofaSecret")
|
||||
if secretRaw == nil {
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_failed_get_secret"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security/two_factor/enroll")
|
||||
return
|
||||
}
|
||||
|
||||
secret := secretRaw.(string)
|
||||
if !totp.Validate(form.Passcode, secret) {
|
||||
if !twofaGenerateSecretAndQr(ctx) {
|
||||
return
|
||||
}
|
||||
ctx.Flash.Error(ctx.Tr("settings.passcode_invalid"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security/two_factor/enroll")
|
||||
return
|
||||
}
|
||||
|
||||
t = &auth.TwoFactor{
|
||||
UID: ctx.User.ID,
|
||||
}
|
||||
err = t.SetSecret(secret)
|
||||
if err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to set secret", err)
|
||||
return
|
||||
}
|
||||
token, err := t.GenerateScratchToken()
|
||||
if err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to generate scratch token", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Now we have to delete the secrets - because if we fail to insert then it's highly likely that they have already been used
|
||||
// If we can detect the unique constraint failure below we can move this to after the NewTwoFactor
|
||||
if err := ctx.Session.Delete("twofaSecret"); err != nil {
|
||||
// tolerate this failure - it's more important to continue
|
||||
log.Error("Unable to delete twofaSecret from the session: Error: %v", err)
|
||||
}
|
||||
if err := ctx.Session.Delete("twofaUri"); err != nil {
|
||||
// tolerate this failure - it's more important to continue
|
||||
log.Error("Unable to delete twofaUri from the session: Error: %v", err)
|
||||
}
|
||||
if err := ctx.Session.Release(); err != nil {
|
||||
// tolerate this failure - it's more important to continue
|
||||
log.Error("Unable to save changes to the session: %v", err)
|
||||
}
|
||||
|
||||
if err = auth.NewTwoFactor(t); err != nil {
|
||||
// FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us.
|
||||
// If there is a unique constraint fail we should just tolerate the error
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
129
routers/web/user/setting/security/openid.go
Normal file
129
routers/web/user/setting/security/openid.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/auth/openid"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
||||
// OpenIDPost response for change user's openid
|
||||
func OpenIDPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.AddOpenIDForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
if ctx.HasError() {
|
||||
loadSecurityData(ctx)
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsSecurity)
|
||||
return
|
||||
}
|
||||
|
||||
// WARNING: specifying a wrong OpenID here could lock
|
||||
// a user out of her account, would be better to
|
||||
// verify/confirm the new OpenID before storing it
|
||||
|
||||
// Also, consider allowing for multiple OpenID URIs
|
||||
|
||||
id, err := openid.Normalize(form.Openid)
|
||||
if err != nil {
|
||||
loadSecurityData(ctx)
|
||||
|
||||
ctx.RenderWithErr(err.Error(), tplSettingsSecurity, &form)
|
||||
return
|
||||
}
|
||||
form.Openid = id
|
||||
log.Trace("Normalized id: " + id)
|
||||
|
||||
oids, err := user_model.GetUserOpenIDs(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserOpenIDs", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["OpenIDs"] = oids
|
||||
|
||||
// Check that the OpenID is not already used
|
||||
for _, obj := range oids {
|
||||
if obj.URI == id {
|
||||
loadSecurityData(ctx)
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("form.openid_been_used", id), tplSettingsSecurity, &form)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
redirectTo := setting.AppURL + "user/settings/security"
|
||||
url, err := openid.RedirectURL(id, redirectTo, setting.AppURL)
|
||||
if err != nil {
|
||||
loadSecurityData(ctx)
|
||||
|
||||
ctx.RenderWithErr(err.Error(), tplSettingsSecurity, &form)
|
||||
return
|
||||
}
|
||||
ctx.Redirect(url)
|
||||
}
|
||||
|
||||
func settingsOpenIDVerify(ctx *context.Context) {
|
||||
log.Trace("Incoming call to: " + ctx.Req.URL.String())
|
||||
|
||||
fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
|
||||
log.Trace("Full URL: " + fullURL)
|
||||
|
||||
id, err := openid.Verify(fullURL)
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSettingsSecurity, &forms.AddOpenIDForm{
|
||||
Openid: id,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Verified ID: " + id)
|
||||
|
||||
oid := &user_model.UserOpenID{UID: ctx.User.ID, URI: id}
|
||||
if err = user_model.AddUserOpenID(oid); err != nil {
|
||||
if user_model.IsErrOpenIDAlreadyUsed(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("form.openid_been_used", id), tplSettingsSecurity, &forms.AddOpenIDForm{Openid: id})
|
||||
return
|
||||
}
|
||||
ctx.ServerError("AddUserOpenID", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Associated OpenID %s to user %s", id, ctx.User.Name)
|
||||
ctx.Flash.Success(ctx.Tr("settings.add_openid_success"))
|
||||
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
|
||||
// DeleteOpenID response for delete user's openid
|
||||
func DeleteOpenID(ctx *context.Context) {
|
||||
if err := user_model.DeleteUserOpenID(&user_model.UserOpenID{ID: ctx.FormInt64("id"), UID: ctx.User.ID}); err != nil {
|
||||
ctx.ServerError("DeleteUserOpenID", err)
|
||||
return
|
||||
}
|
||||
log.Trace("OpenID address deleted: %s", ctx.User.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.openid_deletion_success"))
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/security",
|
||||
})
|
||||
}
|
||||
|
||||
// ToggleOpenIDVisibility response for toggle visibility of user's openid
|
||||
func ToggleOpenIDVisibility(ctx *context.Context) {
|
||||
if err := user_model.ToggleUserOpenIDVisibility(ctx.FormInt64("id")); err != nil {
|
||||
ctx.ServerError("ToggleUserOpenIDVisibility", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
117
routers/web/user/setting/security/security.go
Normal file
117
routers/web/user/setting/security/security.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
tplSettingsSecurity base.TplName = "user/settings/security/security"
|
||||
tplSettingsTwofaEnroll base.TplName = "user/settings/security/twofa_enroll"
|
||||
)
|
||||
|
||||
// Security render change user's password page and 2FA
|
||||
func Security(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
ctx.Data["RequireU2F"] = true
|
||||
|
||||
if ctx.FormString("openid.return_to") != "" {
|
||||
settingsOpenIDVerify(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
loadSecurityData(ctx)
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsSecurity)
|
||||
}
|
||||
|
||||
// DeleteAccountLink delete a single account link
|
||||
func DeleteAccountLink(ctx *context.Context) {
|
||||
id := ctx.FormInt64("id")
|
||||
if id <= 0 {
|
||||
ctx.Flash.Error("Account link id is not given")
|
||||
} else {
|
||||
if _, err := user_model.RemoveAccountLink(ctx.User, id); err != nil {
|
||||
ctx.Flash.Error("RemoveAccountLink: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success"))
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/security",
|
||||
})
|
||||
}
|
||||
|
||||
func loadSecurityData(ctx *context.Context) {
|
||||
enrolled, err := auth.HasTwoFactorByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["TOTPEnrolled"] = enrolled
|
||||
|
||||
ctx.Data["U2FRegistrations"], err = auth.GetU2FRegistrationsByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetU2FRegistrationsByUID", err)
|
||||
return
|
||||
}
|
||||
|
||||
tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{UserID: ctx.User.ID})
|
||||
if err != nil {
|
||||
ctx.ServerError("ListAccessTokens", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Tokens"] = tokens
|
||||
|
||||
accountLinks, err := user_model.ListAccountLinks(ctx.User)
|
||||
if err != nil {
|
||||
ctx.ServerError("ListAccountLinks", err)
|
||||
return
|
||||
}
|
||||
|
||||
// map the provider display name with the AuthSource
|
||||
sources := make(map[*auth.Source]string)
|
||||
for _, externalAccount := range accountLinks {
|
||||
if authSource, err := auth.GetSourceByID(externalAccount.LoginSourceID); err == nil {
|
||||
var providerDisplayName string
|
||||
|
||||
type DisplayNamed interface {
|
||||
DisplayName() string
|
||||
}
|
||||
|
||||
type Named interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
if displayNamed, ok := authSource.Cfg.(DisplayNamed); ok {
|
||||
providerDisplayName = displayNamed.DisplayName()
|
||||
} else if named, ok := authSource.Cfg.(Named); ok {
|
||||
providerDisplayName = named.Name()
|
||||
} else {
|
||||
providerDisplayName = authSource.Name
|
||||
}
|
||||
sources[authSource] = providerDisplayName
|
||||
}
|
||||
}
|
||||
ctx.Data["AccountLinks"] = sources
|
||||
|
||||
openid, err := user_model.GetUserOpenIDs(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserOpenIDs", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["OpenIDs"] = openid
|
||||
}
|
111
routers/web/user/setting/security/u2f.go
Normal file
111
routers/web/user/setting/security/u2f.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
||||
"github.com/tstranex/u2f"
|
||||
)
|
||||
|
||||
// U2FRegister initializes the u2f registration procedure
|
||||
func U2FRegister(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.U2FRegistrationForm)
|
||||
if form.Name == "" {
|
||||
ctx.Error(http.StatusConflict)
|
||||
return
|
||||
}
|
||||
challenge, err := u2f.NewChallenge(setting.U2F.AppID, setting.U2F.TrustedFacets)
|
||||
if err != nil {
|
||||
ctx.ServerError("NewChallenge", err)
|
||||
return
|
||||
}
|
||||
if err := ctx.Session.Set("u2fChallenge", challenge); err != nil {
|
||||
ctx.ServerError("Unable to set session key for u2fChallenge", err)
|
||||
return
|
||||
}
|
||||
regs, err := auth.GetU2FRegistrationsByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetU2FRegistrationsByUID", err)
|
||||
return
|
||||
}
|
||||
for _, reg := range regs {
|
||||
if reg.Name == form.Name {
|
||||
ctx.Error(http.StatusConflict, "Name already taken")
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := ctx.Session.Set("u2fName", form.Name); err != nil {
|
||||
ctx.ServerError("Unable to set session key for u2fName", err)
|
||||
return
|
||||
}
|
||||
// Here we're just going to try to release the session early
|
||||
if err := ctx.Session.Release(); err != nil {
|
||||
// we'll tolerate errors here as they *should* get saved elsewhere
|
||||
log.Error("Unable to save changes to the session: %v", err)
|
||||
}
|
||||
ctx.JSON(http.StatusOK, u2f.NewWebRegisterRequest(challenge, regs.ToRegistrations()))
|
||||
}
|
||||
|
||||
// U2FRegisterPost receives the response of the security key
|
||||
func U2FRegisterPost(ctx *context.Context) {
|
||||
response := web.GetForm(ctx).(*u2f.RegisterResponse)
|
||||
challSess := ctx.Session.Get("u2fChallenge")
|
||||
u2fName := ctx.Session.Get("u2fName")
|
||||
if challSess == nil || u2fName == nil {
|
||||
ctx.ServerError("U2FRegisterPost", errors.New("not in U2F session"))
|
||||
return
|
||||
}
|
||||
challenge := challSess.(*u2f.Challenge)
|
||||
name := u2fName.(string)
|
||||
config := &u2f.Config{
|
||||
// Chrome 66+ doesn't return the device's attestation
|
||||
// certificate by default.
|
||||
SkipAttestationVerify: true,
|
||||
}
|
||||
reg, err := u2f.Register(*response, *challenge, config)
|
||||
if err != nil {
|
||||
ctx.ServerError("u2f.Register", err)
|
||||
return
|
||||
}
|
||||
if _, err = auth.CreateRegistration(ctx.User.ID, name, reg); err != nil {
|
||||
ctx.ServerError("u2f.Register", err)
|
||||
return
|
||||
}
|
||||
ctx.Status(200)
|
||||
}
|
||||
|
||||
// U2FDelete deletes an security key by id
|
||||
func U2FDelete(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.U2FDeleteForm)
|
||||
reg, err := auth.GetU2FRegistrationByID(form.ID)
|
||||
if err != nil {
|
||||
if auth.IsErrU2FRegistrationNotExist(err) {
|
||||
ctx.Status(200)
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetU2FRegistrationByID", err)
|
||||
return
|
||||
}
|
||||
if reg.UserID != ctx.User.ID {
|
||||
ctx.Status(401)
|
||||
return
|
||||
}
|
||||
if err := auth.DeleteRegistration(reg); err != nil {
|
||||
ctx.ServerError("DeleteRegistration", err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/security",
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user