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