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 }