github.com/navikt/knorten@v0.0.0-20240419132333-1333f46ed8b6/pkg/api/handlers/auth_handlers.go (about)

     1  package handlers
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/navikt/knorten/pkg/common"
    11  
    12  	"github.com/navikt/knorten/pkg/api/service"
    13  
    14  	"github.com/gin-contrib/sessions"
    15  
    16  	"github.com/google/uuid"
    17  
    18  	"github.com/navikt/knorten/pkg/database"
    19  	"github.com/sirupsen/logrus"
    20  
    21  	"github.com/navikt/knorten/pkg/api/auth"
    22  
    23  	"github.com/gin-gonic/gin"
    24  	"github.com/navikt/knorten/pkg/config"
    25  )
    26  
    27  const (
    28  	tokenLength   = 32
    29  	sessionLength = 1 * time.Hour
    30  )
    31  
    32  type AuthHandler struct {
    33  	authService service.AuthService
    34  	cookies     config.Cookies
    35  	log         *logrus.Entry
    36  	repo        *database.Repo
    37  	loginPage   string
    38  }
    39  
    40  func NewAuthHandler(authService service.AuthService, loginPage string, cookies config.Cookies, log *logrus.Entry, repo *database.Repo) *AuthHandler {
    41  	return &AuthHandler{
    42  		authService: authService,
    43  		cookies:     cookies,
    44  		log:         log,
    45  		loginPage:   loginPage,
    46  		repo:        repo,
    47  	}
    48  }
    49  
    50  func (h *AuthHandler) LogoutHandler() gin.HandlerFunc {
    51  	return func(ctx *gin.Context) {
    52  		token, err := ctx.Cookie(h.cookies.Session.Name)
    53  		if err != nil {
    54  			h.log.WithError(err).Error("unable to get session cookie")
    55  			ctx.Redirect(http.StatusSeeOther, "/")
    56  
    57  			return
    58  		}
    59  
    60  		deleteCookie(ctx, h.cookies.Session.Name, h.cookies.Session.Domain, h.cookies.Session.Path)
    61  
    62  		err = h.authService.DeleteSession(ctx.Request.Context(), token)
    63  		if err != nil {
    64  			session := sessions.Default(ctx)
    65  			session.AddFlash(err.Error())
    66  			err := session.Save()
    67  			if err != nil {
    68  				h.log.WithError(err).Error("problem saving session")
    69  				ctx.Redirect(http.StatusSeeOther, "/")
    70  				return
    71  			}
    72  			ctx.Redirect(http.StatusSeeOther, "/")
    73  
    74  			return
    75  		}
    76  
    77  		ctx.Redirect(http.StatusSeeOther, h.loginPage)
    78  	}
    79  }
    80  
    81  func (h *AuthHandler) CallbackHandler() gin.HandlerFunc {
    82  	return func(ctx *gin.Context) {
    83  		redirectURL, err := h.callback(ctx)
    84  		if err != nil {
    85  			session := sessions.Default(ctx)
    86  			session.AddFlash(err.Error())
    87  			err := session.Save()
    88  			if err != nil {
    89  				h.log.WithError(err).Error("problem saving session")
    90  				ctx.Redirect(http.StatusSeeOther, "/")
    91  				return
    92  			}
    93  			ctx.Redirect(http.StatusSeeOther, "/")
    94  			return
    95  		}
    96  
    97  		ctx.Redirect(http.StatusSeeOther, redirectURL)
    98  	}
    99  }
   100  
   101  func (h *AuthHandler) callback(ctx *gin.Context) (string, error) {
   102  	loginPage := "/oversikt"
   103  
   104  	redirectURI, _ := ctx.Cookie(h.cookies.Redirect.Name)
   105  	if redirectURI != "" {
   106  		loginPage = redirectURI
   107  	}
   108  
   109  	if strings.HasPrefix(ctx.Request.Host, "localhost") {
   110  		loginPage = "http://localhost:8080" + loginPage
   111  	}
   112  
   113  	deleteCookie(ctx, h.cookies.Redirect.Name, h.cookies.Redirect.Domain, h.cookies.Redirect.Path)
   114  
   115  	code := ctx.Request.URL.Query().Get("code")
   116  	if len(code) == 0 {
   117  		return loginPage + "?error=unauthenticated", errors.New("unauthenticated")
   118  	}
   119  
   120  	oauthCookie, err := ctx.Cookie(h.cookies.OauthState.Name)
   121  	if err != nil {
   122  		h.log.Infof("Missing oauth state cookie: %v", err)
   123  		return loginPage + "?error=invalid-state", errors.New("invalid state")
   124  	}
   125  
   126  	deleteCookie(ctx, h.cookies.OauthState.Name, h.cookies.OauthState.Domain, h.cookies.OauthState.Path)
   127  
   128  	state := ctx.Request.URL.Query().Get("state")
   129  	if state != oauthCookie {
   130  		h.log.Info("Incoming state does not match local state")
   131  		return loginPage + "?error=invalid-state", errors.New("invalid state")
   132  	}
   133  
   134  	sess, err := h.authService.CreateSession(ctx.Request.Context(), code)
   135  	if err != nil {
   136  		h.log.WithError(err).Error("unable to create session")
   137  
   138  		return loginPage + "?error=unauthenticated", fmt.Errorf("unable to create session: %w", err)
   139  	}
   140  
   141  	ctx.SetCookie(
   142  		h.cookies.Session.Name,
   143  		sess.Token,
   144  		h.cookies.Session.MaxAge,
   145  		h.cookies.Session.Path,
   146  		h.cookies.Session.Domain,
   147  		h.cookies.Session.Secure,
   148  		h.cookies.Session.HttpOnly,
   149  	)
   150  
   151  	return loginPage, nil
   152  }
   153  
   154  func deleteCookie(ctx *gin.Context, name, host, path string) {
   155  	ctx.SetCookie(name, "", -1, path, host, true, true)
   156  }
   157  
   158  func (h *AuthHandler) LoginHandler(dryRun bool) gin.HandlerFunc {
   159  	return func(ctx *gin.Context) {
   160  		if dryRun {
   161  			if err := h.createDryRunSession(ctx); err != nil {
   162  				h.log.Error("creating dryrun session")
   163  			}
   164  
   165  			ctx.Redirect(http.StatusSeeOther, "http://localhost:8080/oversikt")
   166  
   167  			return
   168  		}
   169  
   170  		ctx.SetSameSite(h.cookies.Redirect.GetSameSite())
   171  		ctx.SetCookie(
   172  			h.cookies.Redirect.Name,
   173  			ctx.Request.URL.Query().Get("redirect_uri"),
   174  			h.cookies.Redirect.MaxAge,
   175  			h.cookies.Redirect.Path,
   176  			h.cookies.Redirect.Domain,
   177  			h.cookies.Redirect.Secure,
   178  			h.cookies.Redirect.HttpOnly,
   179  		)
   180  
   181  		oauthState := uuid.New().String()
   182  		ctx.SetSameSite(h.cookies.OauthState.GetSameSite())
   183  		ctx.SetCookie(
   184  			h.cookies.OauthState.Name,
   185  			oauthState,
   186  			h.cookies.OauthState.MaxAge,
   187  			h.cookies.OauthState.Path,
   188  			h.cookies.OauthState.Domain,
   189  			h.cookies.OauthState.Secure,
   190  			h.cookies.OauthState.HttpOnly,
   191  		)
   192  
   193  		ctx.Redirect(http.StatusSeeOther, h.authService.GetLoginURL(oauthState))
   194  	}
   195  }
   196  
   197  func (h *AuthHandler) createDryRunSession(ctx *gin.Context) error {
   198  	session := &auth.Session{
   199  		Token:       common.GenerateSecureToken(tokenLength),
   200  		Expires:     time.Now().Add(sessionLength),
   201  		AccessToken: "",
   202  		IsAdmin:     true,
   203  	}
   204  
   205  	if err := h.repo.SessionCreate(ctx, session); err != nil {
   206  		h.log.WithError(err).Error("unable to create session")
   207  		return errors.New("unable to create session")
   208  	}
   209  
   210  	ctx.SetCookie(
   211  		h.cookies.Session.Name,
   212  		session.Token,
   213  		h.cookies.Session.MaxAge,
   214  		h.cookies.Session.Path,
   215  		h.cookies.Session.Domain,
   216  		h.cookies.Session.Secure,
   217  		h.cookies.Session.HttpOnly,
   218  	)
   219  
   220  	return nil
   221  }