summaryrefslogtreecommitdiff
path: root/internal/middlewares/require_auth.go
diff options
context:
space:
mode:
authorJames Elliott <james-d-elliott@users.noreply.github.com>2023-10-23 15:32:15 +1100
committerJames Elliott <james-d-elliott@users.noreply.github.com>2024-03-04 20:29:11 +1100
commitd62d79e581046876638ab61b2125ad5a0e9f925f (patch)
tree5581a5256c06f9c7f0c3f67c98619fd4fa82ecce /internal/middlewares/require_auth.go
parent320b96486cb26ff36e79a6aa5c6aa6ddb4cc0e41 (diff)
feat(web): second factor identity verification
This adds customizable options for identity verification where the user can either be required to skip the identity verification requirement when they have performed second factor authentication, or requiring second factor authentication in addition to the identity verification. There are 3 distinct modes. You can require both second factor authentication and the one-time code (recommended), you can require just the one-time code (default), or you can require either second factor authentication or a one-time code (discouraged). Closes #135 Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
Diffstat (limited to 'internal/middlewares/require_auth.go')
-rw-r--r--internal/middlewares/require_auth.go126
1 files changed, 126 insertions, 0 deletions
diff --git a/internal/middlewares/require_auth.go b/internal/middlewares/require_auth.go
new file mode 100644
index 000000000..3ae12db36
--- /dev/null
+++ b/internal/middlewares/require_auth.go
@@ -0,0 +1,126 @@
+package middlewares
+
+import (
+ "github.com/valyala/fasthttp"
+
+ "github.com/authelia/authelia/v4/internal/authentication"
+ "github.com/authelia/authelia/v4/internal/model"
+ "github.com/authelia/authelia/v4/internal/session"
+)
+
+// Require1FA check if user has enough permissions to execute the next handler.
+func Require1FA(next RequestHandler) RequestHandler {
+ return func(ctx *AutheliaCtx) {
+ if s, err := ctx.GetSession(); err != nil || s.AuthenticationLevel < authentication.OneFactor {
+ ctx.ReplyForbidden()
+ return
+ }
+
+ next(ctx)
+ }
+}
+
+type ElevatedForbiddenResponse struct {
+ Elevation bool `json:"elevation"`
+ FirstFactor bool `json:"first_factor"`
+ SecondFactor bool `json:"second_factor"`
+}
+
+func RequireElevated(next RequestHandler) RequestHandler {
+ return func(ctx *AutheliaCtx) {
+ var (
+ userSession session.UserSession
+ err error
+ )
+
+ if userSession, err = ctx.GetSession(); err != nil {
+ ctx.Logger.WithError(err).Error("Error occurred attempting to lookup user session during an elevation check.")
+
+ if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{FirstFactor: true}}, fasthttp.StatusForbidden); err != nil {
+ ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
+ }
+
+ return
+ }
+
+ if userSession.AuthenticationLevel < authentication.OneFactor {
+ ctx.Logger.Warn("An anonymous user attempted to access an elevated protected endpoint.")
+
+ if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{FirstFactor: true}}, fasthttp.StatusForbidden); err != nil {
+ ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
+ }
+
+ return
+ }
+
+ if ctx.Configuration.IdentityValidation.ElevatedSession.SkipSecondFactor && userSession.AuthenticationLevel >= authentication.TwoFactor {
+ ctx.Logger.WithFields(map[string]any{"user": userSession.Username}).Trace("The user session elevation was not checked as the user has performed second factor authentication and the policy to skip this is enabled.")
+
+ next(ctx)
+
+ return
+ }
+
+ if ctx.Configuration.IdentityValidation.ElevatedSession.RequireSecondFactor && userSession.AuthenticationLevel < authentication.TwoFactor {
+ var info model.UserInfo
+
+ if info, err = ctx.Providers.StorageProvider.LoadUserInfo(ctx, userSession.Username); err != nil {
+ ctx.Logger.WithError(err).Error("Error occurred attempting to lookup user information during a elevation check.")
+
+ if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{SecondFactor: true}}, fasthttp.StatusForbidden); err != nil {
+ ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
+ }
+
+ return
+ }
+
+ if info.HasTOTP || info.HasWebAuthn || info.HasDuo {
+ ctx.Logger.WithFields(map[string]any{"user": userSession.Username}).Info("The user session elevation was not checked as the user must have also performed second factor authentication.")
+
+ if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{SecondFactor: true}}, fasthttp.StatusForbidden); err != nil {
+ ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
+ }
+
+ return
+ }
+ }
+
+ if userSession.Elevations.User == nil {
+ if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{Elevation: true}}, fasthttp.StatusForbidden); err != nil {
+ ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
+ }
+
+ return
+ }
+
+ invalid := false
+
+ if ctx.GetClock().Now().After(userSession.Elevations.User.Expires) {
+ invalid = true
+
+ ctx.Logger.WithFields(map[string]any{"user": userSession.Username, "expired": userSession.Elevations.User.Expires.Unix()}).Info("The user session elevation was expired. It will be destroyed and the users access will be forbidden.")
+ }
+
+ if !ctx.RemoteIP().Equal(userSession.Elevations.User.RemoteIP) {
+ invalid = true
+
+ ctx.Logger.WithFields(map[string]any{"user": userSession.Username, "expected_ip": userSession.Elevations.User.RemoteIP.String()}).Warn("The user session elevation did not have a matching IP. It will be destroyed and the users access will be forbidden.")
+ }
+
+ if invalid {
+ userSession.Elevations.User = nil
+
+ if err = ctx.SaveSession(userSession); err != nil {
+ ctx.Logger.WithError(err).Error("Error occurred trying to save the user session after a policy constraint violation occurred.")
+ }
+
+ if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{Elevation: true}}, fasthttp.StatusForbidden); err != nil {
+ ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
+ }
+
+ return
+ }
+
+ next(ctx)
+ }
+}