code.gitea.io/gitea@v1.21.7/routers/web/auth/2fa.go (about)

     1  // Copyright 2017 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package auth
     5  
     6  import (
     7  	"errors"
     8  	"net/http"
     9  
    10  	"code.gitea.io/gitea/models/auth"
    11  	user_model "code.gitea.io/gitea/models/user"
    12  	"code.gitea.io/gitea/modules/base"
    13  	"code.gitea.io/gitea/modules/context"
    14  	"code.gitea.io/gitea/modules/setting"
    15  	"code.gitea.io/gitea/modules/web"
    16  	"code.gitea.io/gitea/services/externalaccount"
    17  	"code.gitea.io/gitea/services/forms"
    18  )
    19  
    20  var (
    21  	tplTwofa        base.TplName = "user/auth/twofa"
    22  	tplTwofaScratch base.TplName = "user/auth/twofa_scratch"
    23  )
    24  
    25  // TwoFactor shows the user a two-factor authentication page.
    26  func TwoFactor(ctx *context.Context) {
    27  	ctx.Data["Title"] = ctx.Tr("twofa")
    28  
    29  	// Check auto-login.
    30  	if checkAutoLogin(ctx) {
    31  		return
    32  	}
    33  
    34  	// Ensure user is in a 2FA session.
    35  	if ctx.Session.Get("twofaUid") == nil {
    36  		ctx.ServerError("UserSignIn", errors.New("not in 2FA session"))
    37  		return
    38  	}
    39  
    40  	ctx.HTML(http.StatusOK, tplTwofa)
    41  }
    42  
    43  // TwoFactorPost validates a user's two-factor authentication token.
    44  func TwoFactorPost(ctx *context.Context) {
    45  	form := web.GetForm(ctx).(*forms.TwoFactorAuthForm)
    46  	ctx.Data["Title"] = ctx.Tr("twofa")
    47  
    48  	// Ensure user is in a 2FA session.
    49  	idSess := ctx.Session.Get("twofaUid")
    50  	if idSess == nil {
    51  		ctx.ServerError("UserSignIn", errors.New("not in 2FA session"))
    52  		return
    53  	}
    54  
    55  	id := idSess.(int64)
    56  	twofa, err := auth.GetTwoFactorByUID(ctx, id)
    57  	if err != nil {
    58  		ctx.ServerError("UserSignIn", err)
    59  		return
    60  	}
    61  
    62  	// Validate the passcode with the stored TOTP secret.
    63  	ok, err := twofa.ValidateTOTP(form.Passcode)
    64  	if err != nil {
    65  		ctx.ServerError("UserSignIn", err)
    66  		return
    67  	}
    68  
    69  	if ok && twofa.LastUsedPasscode != form.Passcode {
    70  		remember := ctx.Session.Get("twofaRemember").(bool)
    71  		u, err := user_model.GetUserByID(ctx, id)
    72  		if err != nil {
    73  			ctx.ServerError("UserSignIn", err)
    74  			return
    75  		}
    76  
    77  		if ctx.Session.Get("linkAccount") != nil {
    78  			err = externalaccount.LinkAccountFromStore(ctx, ctx.Session, u)
    79  			if err != nil {
    80  				ctx.ServerError("UserSignIn", err)
    81  				return
    82  			}
    83  		}
    84  
    85  		twofa.LastUsedPasscode = form.Passcode
    86  		if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
    87  			ctx.ServerError("UserSignIn", err)
    88  			return
    89  		}
    90  
    91  		handleSignIn(ctx, u, remember)
    92  		return
    93  	}
    94  
    95  	ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{})
    96  }
    97  
    98  // TwoFactorScratch shows the scratch code form for two-factor authentication.
    99  func TwoFactorScratch(ctx *context.Context) {
   100  	ctx.Data["Title"] = ctx.Tr("twofa_scratch")
   101  
   102  	// Check auto-login.
   103  	if checkAutoLogin(ctx) {
   104  		return
   105  	}
   106  
   107  	// Ensure user is in a 2FA session.
   108  	if ctx.Session.Get("twofaUid") == nil {
   109  		ctx.ServerError("UserSignIn", errors.New("not in 2FA session"))
   110  		return
   111  	}
   112  
   113  	ctx.HTML(http.StatusOK, tplTwofaScratch)
   114  }
   115  
   116  // TwoFactorScratchPost validates and invalidates a user's two-factor scratch token.
   117  func TwoFactorScratchPost(ctx *context.Context) {
   118  	form := web.GetForm(ctx).(*forms.TwoFactorScratchAuthForm)
   119  	ctx.Data["Title"] = ctx.Tr("twofa_scratch")
   120  
   121  	// Ensure user is in a 2FA session.
   122  	idSess := ctx.Session.Get("twofaUid")
   123  	if idSess == nil {
   124  		ctx.ServerError("UserSignIn", errors.New("not in 2FA session"))
   125  		return
   126  	}
   127  
   128  	id := idSess.(int64)
   129  	twofa, err := auth.GetTwoFactorByUID(ctx, id)
   130  	if err != nil {
   131  		ctx.ServerError("UserSignIn", err)
   132  		return
   133  	}
   134  
   135  	// Validate the passcode with the stored TOTP secret.
   136  	if twofa.VerifyScratchToken(form.Token) {
   137  		// Invalidate the scratch token.
   138  		_, err = twofa.GenerateScratchToken()
   139  		if err != nil {
   140  			ctx.ServerError("UserSignIn", err)
   141  			return
   142  		}
   143  		if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
   144  			ctx.ServerError("UserSignIn", err)
   145  			return
   146  		}
   147  
   148  		remember := ctx.Session.Get("twofaRemember").(bool)
   149  		u, err := user_model.GetUserByID(ctx, id)
   150  		if err != nil {
   151  			ctx.ServerError("UserSignIn", err)
   152  			return
   153  		}
   154  
   155  		handleSignInFull(ctx, u, remember, false)
   156  		if ctx.Written() {
   157  			return
   158  		}
   159  		ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
   160  		ctx.Redirect(setting.AppSubURL + "/user/settings/security")
   161  		return
   162  	}
   163  
   164  	ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplTwofaScratch, forms.TwoFactorScratchAuthForm{})
   165  }