Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two factor authentication support #630

Merged
merged 11 commits into from
Jan 16, 2017
22 changes: 11 additions & 11 deletions cmd/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,11 @@ func runWeb(ctx *cli.Context) error {
m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
m.Get("/reset_password", user.ResetPasswd)
m.Post("/reset_password", user.ResetPasswdPost)
m.Group("/2fa", func() {
m.Get("", user.ShowTwofa)
m.Post("", bindIgnErr(auth.TwofaAuthForm{}), user.TwofaPost)
m.Get("/scratch", user.TwofaScratch)
m.Post("/scratch", bindIgnErr(auth.TwofaScratchAuthForm{}), user.TwofaScratchPost)
m.Group("/two_factor", func() {
m.Get("", user.TwoFactor)
m.Post("", bindIgnErr(auth.TwoFactorAuthForm{}), user.TwoFactorPost)
m.Get("/scratch", user.TwoFactorScratch)
m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost)
})
}, reqSignOut)

Expand All @@ -229,12 +229,12 @@ func runWeb(ctx *cli.Context) error {
Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
m.Post("/applications/delete", user.SettingsDeleteApplication)
m.Route("/delete", "GET,POST", user.SettingsDelete)
m.Group("/2fa", func() {
m.Get("", user.SettingsTwofa)
m.Post("/regenerate_scratch", user.SettingsTwofaRegenerateScratch)
m.Post("/disable", user.SettingsTwofaDisable)
m.Get("/enroll", user.SettingsTwofaEnroll)
m.Post("/enroll", bindIgnErr(auth.TwofaAuthForm{}), user.SettingsTwofaEnrollPost)
m.Group("/two_factor", func() {
m.Get("", user.SettingsTwoFactor)
m.Post("/regenerate_scratch", user.SettingsTwoFactorRegenerateScratch)
m.Post("/disable", user.SettingsTwoFactorDisable)
m.Get("/enroll", user.SettingsTwoFactorEnroll)
m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), user.SettingsTwoFactorEnrollPost)
})
}, reqSignIn, func(ctx *context.Context) {
ctx.Data["PageIsUserSettings"] = true
Expand Down
12 changes: 6 additions & 6 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -791,18 +791,18 @@ func (err ErrTeamAlreadyExist) Error() string {
// Two-factor authentication
//

// ErrTwofaNotEnrolled indicates that a user is not enrolled in two-factor authentication.
type ErrTwofaNotEnrolled struct {
// ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication.
type ErrTwoFactorNotEnrolled struct {
UID int64
}

// IsErrTwofaNotEnrolled checks if an error is a ErrTwofaNotEnrolled.
func IsErrTwofaNotEnrolled(err error) bool {
_, ok := err.(ErrTwofaNotEnrolled)
// IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled.
func IsErrTwoFactorNotEnrolled(err error) bool {
_, ok := err.(ErrTwoFactorNotEnrolled)
return ok
}

func (err ErrTwofaNotEnrolled) Error() string {
func (err ErrTwoFactorNotEnrolled) Error() string {
return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID)
}

Expand Down
2 changes: 1 addition & 1 deletion models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func init() {
new(Notification),
new(IssueUser),
new(LFSMetaObject),
new(Twofa),
new(TwoFactor),
)

gonicNames := []string{"SSL", "UID"}
Expand Down
44 changes: 22 additions & 22 deletions models/twofa.go → models/twofactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
"code.gitea.io/gitea/modules/setting"
)

// Twofa represents a two-factor authentication token.
type Twofa struct {
// TwoFactor represents a two-factor authentication token.
type TwoFactor struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"UNIQUE INDEX"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's no need for both UNIQUE and INDEX, chose one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Secret string
Expand All @@ -32,17 +32,17 @@ type Twofa struct {
}

// BeforeInsert will be invoked by XORM before inserting a record representing this object.
func (t *Twofa) BeforeInsert() {
func (t *TwoFactor) BeforeInsert() {
t.CreatedUnix = time.Now().Unix()
}

// BeforeUpdate is invoked from XORM before updating this object.
func (t *Twofa) BeforeUpdate() {
func (t *TwoFactor) BeforeUpdate() {
t.UpdatedUnix = time.Now().Unix()
}

// AfterSet is invoked from XORM after setting the value of a field of this object.
func (t *Twofa) AfterSet(colName string, _ xorm.Cell) {
func (t *TwoFactor) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "created_unix":
t.Created = time.Unix(t.CreatedUnix, 0).Local()
Expand All @@ -52,7 +52,7 @@ func (t *Twofa) AfterSet(colName string, _ xorm.Cell) {
}

// GenerateScratchToken recreates the scratch token the user is using.
func (t *Twofa) GenerateScratchToken() error {
func (t *TwoFactor) GenerateScratchToken() error {
token, err := base.GetRandomString(8)
if err != nil {
return err
Expand All @@ -62,20 +62,20 @@ func (t *Twofa) GenerateScratchToken() error {
}

// VerifyScratchToken verifies if the specified scratch token is valid.
func (t *Twofa) VerifyScratchToken(token string) bool {
func (t *TwoFactor) VerifyScratchToken(token string) bool {
if len(token) == 0 {
return false
}
return subtle.ConstantTimeCompare([]byte(token), []byte(t.ScratchToken)) == 1
}

func (t *Twofa) getEncryptionKey() []byte {
func (t *TwoFactor) getEncryptionKey() []byte {
k := md5.Sum([]byte(setting.SecretKey))
return k[:]
}

// SetSecret sets the 2FA secret.
func (t *Twofa) SetSecret(secret string) error {
func (t *TwoFactor) SetSecret(secret string) error {
secretBytes, err := com.AESEncrypt(t.getEncryptionKey(), []byte(secret))
if err != nil {
return err
Expand All @@ -85,7 +85,7 @@ func (t *Twofa) SetSecret(secret string) error {
}

// ValidateTOTP validates the provided passcode.
func (t *Twofa) ValidateTOTP(passcode string) (bool, error) {
func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
decodedStoredSecret, err := base64.StdEncoding.DecodeString(t.Secret)
if err != nil {
return false, err
Expand All @@ -98,8 +98,8 @@ func (t *Twofa) ValidateTOTP(passcode string) (bool, error) {
return totp.Validate(passcode, secretStr), nil
}

// NewTwofa creates a new two-factor authentication token.
func NewTwofa(t *Twofa) error {
// NewTwoFactor creates a new two-factor authentication token.
func NewTwoFactor(t *TwoFactor) error {
err := t.GenerateScratchToken()
if err != nil {
return err
Expand All @@ -108,34 +108,34 @@ func NewTwofa(t *Twofa) error {
return err
}

// UpdateTwofa updates a two-factor authentication token.
func UpdateTwofa(t *Twofa) error {
// UpdateTwoFactor updates a two-factor authentication token.
func UpdateTwoFactor(t *TwoFactor) error {
_, err := x.Id(t.ID).AllCols().Update(t)
return err
}

// GetTwofaByUID returns the two-factor authentication token associated with
// GetTwoFactorByUID returns the two-factor authentication token associated with
// the user, if any.
func GetTwofaByUID(uid int64) (*Twofa, error) {
twofa := &Twofa{UID: uid}
func GetTwoFactorByUID(uid int64) (*TwoFactor, error) {
twofa := &TwoFactor{UID: uid}
has, err := x.Get(twofa)
if err != nil {
return nil, err
} else if !has {
return nil, ErrTwofaNotEnrolled{uid}
return nil, ErrTwoFactorNotEnrolled{uid}
}
return twofa, nil
}

// DeleteTwofaByID deletes two-factor authentication token by given ID.
func DeleteTwofaByID(id, userID int64) error {
cnt, err := x.Id(id).Delete(&Twofa{
// DeleteTwoFactorByID deletes two-factor authentication token by given ID.
func DeleteTwoFactorByID(id, userID int64) error {
cnt, err := x.Id(id).Delete(&TwoFactor{
UID: userID,
})
if err != nil {
return err
} else if cnt != 1 {
return ErrTwofaNotEnrolled{userID}
return ErrTwoFactorNotEnrolled{userID}
}
return nil
}
12 changes: 6 additions & 6 deletions modules/auth/user_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,22 +174,22 @@ func (f *NewAccessTokenForm) Validate(ctx *macaron.Context, errs binding.Errors)
return validate(errs, ctx.Data, f, ctx.Locale)
}

// TwofaAuthForm for logging in with 2FA token.
type TwofaAuthForm struct {
// TwoFactorAuthForm for logging in with 2FA token.
type TwoFactorAuthForm struct {
Passcode string `binding:"Required"`
}

// Validate valideates the fields
func (f *TwofaAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
func (f *TwoFactorAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

// TwofaScratchAuthForm for logging in with 2FA scratch token.
type TwofaScratchAuthForm struct {
// TwoFactorScratchAuthForm for logging in with 2FA scratch token.
type TwoFactorScratchAuthForm struct {
Token string `binding:"Required"`
}

// Validate valideates the fields
func (f *TwofaScratchAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
func (f *TwoFactorScratchAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
34 changes: 17 additions & 17 deletions routers/user/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {

// If this user is enrolled in 2FA, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page.
_, err = models.GetTwofaByUID(u.ID)
_, err = models.GetTwoFactorByUID(u.ID)
if err != nil {
if models.IsErrTwofaNotEnrolled(err) {
if models.IsErrTwoFactorNotEnrolled(err) {
handleSignIn(ctx, u, form.Remember)
} else {
ctx.Handle(500, "UserSignIn", err)
Expand All @@ -146,11 +146,11 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {
// User needs to use 2FA, save data and redirect to 2FA page.
ctx.Session.Set("twofaUid", u.ID)
ctx.Session.Set("twofaRemember", form.Remember)
ctx.Redirect(setting.AppSubURL + "/user/2fa")
ctx.Redirect(setting.AppSubURL + "/user/two_factor")
}

// ShowTwofa shows the user a two-factor authentication page.
func ShowTwofa(ctx *context.Context) {
// TwoFactor shows the user a two-factor authentication page.
func TwoFactor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("twofa")

// Check auto-login.
Expand All @@ -167,8 +167,8 @@ func ShowTwofa(ctx *context.Context) {
ctx.HTML(200, tplTwofa)
}

// TwofaPost validates a user's two-factor authentication token.
func TwofaPost(ctx *context.Context, form auth.TwofaAuthForm) {
// TwoFactorPost validates a user's two-factor authentication token.
func TwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
ctx.Data["Title"] = ctx.Tr("twofa")

// Ensure user is in a 2FA session.
Expand All @@ -179,7 +179,7 @@ func TwofaPost(ctx *context.Context, form auth.TwofaAuthForm) {
}

id := idSess.(int64)
twofa, err := models.GetTwofaByUID(id)
twofa, err := models.GetTwoFactorByUID(id)
if err != nil {
ctx.Handle(500, "UserSignIn", err)
return
Expand All @@ -204,11 +204,11 @@ func TwofaPost(ctx *context.Context, form auth.TwofaAuthForm) {
return
}

ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, auth.TwofaAuthForm{})
ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, auth.TwoFactorAuthForm{})
}

// TwofaScratch shows the scratch code form for two-factor authentication.
func TwofaScratch(ctx *context.Context) {
// TwoFactorScratch shows the scratch code form for two-factor authentication.
func TwoFactorScratch(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("twofa_scratch")

// Check auto-login.
Expand All @@ -225,8 +225,8 @@ func TwofaScratch(ctx *context.Context) {
ctx.HTML(200, tplTwofaScratch)
}

// TwofaScratchPost validates and invalidates a user's two-factor scratch token.
func TwofaScratchPost(ctx *context.Context, form auth.TwofaScratchAuthForm) {
// TwoFactorScratchPost validates and invalidates a user's two-factor scratch token.
func TwoFactorScratchPost(ctx *context.Context, form auth.TwoFactorScratchAuthForm) {
ctx.Data["Title"] = ctx.Tr("twofa_scratch")

// Ensure user is in a 2FA session.
Expand All @@ -237,7 +237,7 @@ func TwofaScratchPost(ctx *context.Context, form auth.TwofaScratchAuthForm) {
}

id := idSess.(int64)
twofa, err := models.GetTwofaByUID(id)
twofa, err := models.GetTwoFactorByUID(id)
if err != nil {
ctx.Handle(500, "UserSignIn", err)
return
Expand All @@ -247,7 +247,7 @@ func TwofaScratchPost(ctx *context.Context, form auth.TwofaScratchAuthForm) {
if twofa.VerifyScratchToken(form.Token) {
// Invalidate the scratch token.
twofa.ScratchToken = ""
if err = models.UpdateTwofa(twofa); err != nil {
if err = models.UpdateTwoFactor(twofa); err != nil {
ctx.Handle(500, "UserSignIn", err)
return
}
Expand All @@ -261,11 +261,11 @@ func TwofaScratchPost(ctx *context.Context, form auth.TwofaScratchAuthForm) {

handleSignInFull(ctx, u, remember, false)
ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
ctx.Redirect(setting.AppSubURL + "/user/settings/2fa")
ctx.Redirect(setting.AppSubURL + "/user/settings/two_factor")
return
}

ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplTwofaScratch, auth.TwofaScratchAuthForm{})
ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplTwofaScratch, auth.TwoFactorScratchAuthForm{})
}

// This handles the final part of the sign-in process of the user.
Expand Down
Loading