108 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			108 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package handlers
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/hmac"
 | |
| 	"crypto/sha256"
 | |
| 	"encoding/base64"
 | |
| 	"errors"
 | |
| 	"golias/models"
 | |
| 	"net/http"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // responseWriterWrapper wraps http.ResponseWriter to capture status code
 | |
| type responseWriterWrapper struct {
 | |
| 	http.ResponseWriter
 | |
| 	status int
 | |
| }
 | |
| 
 | |
| func (w *responseWriterWrapper) WriteHeader(status int) {
 | |
| 	w.status = status
 | |
| 	w.ResponseWriter.WriteHeader(status)
 | |
| }
 | |
| 
 | |
| // LogRequests logs all HTTP requests with method, path and duration
 | |
| func LogRequests(next http.Handler) http.Handler {
 | |
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 		start := time.Now()
 | |
| 		// Wrap response writer to capture status code
 | |
| 		ww := &responseWriterWrapper{ResponseWriter: w}
 | |
| 		next.ServeHTTP(ww, r)
 | |
| 		duration := time.Since(start)
 | |
| 		log.Debug("request completed",
 | |
| 			"method", r.Method,
 | |
| 			"path", r.URL.RequestURI(),
 | |
| 			"status", ww.status,
 | |
| 			"duration", duration.String(),
 | |
| 		)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func GetSession(next http.Handler) http.Handler {
 | |
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 		cookieName := "session_token"
 | |
| 		sessionCookie, err := r.Cookie(cookieName)
 | |
| 		if err != nil {
 | |
| 			msg := "auth failed; failed to get session token from cookies"
 | |
| 			log.Debug(msg, "error", err)
 | |
| 			next.ServeHTTP(w, r)
 | |
| 			return
 | |
| 		}
 | |
| 		cookieValueB, err := base64.URLEncoding.
 | |
| 			DecodeString(sessionCookie.Value)
 | |
| 		if err != nil {
 | |
| 			msg := "auth failed; failed to decode b64 cookie"
 | |
| 			log.Debug(msg, "error", err)
 | |
| 			next.ServeHTTP(w, r)
 | |
| 			return
 | |
| 		}
 | |
| 		cookieValue := string(cookieValueB)
 | |
| 		if len(cookieValue) < sha256.Size {
 | |
| 			log.Warn("small cookie", "size", len(cookieValue))
 | |
| 			next.ServeHTTP(w, r)
 | |
| 			return
 | |
| 		}
 | |
| 		// Split apart the signature and original cookie value.
 | |
| 		signature := cookieValue[:sha256.Size]
 | |
| 		sessionToken := cookieValue[sha256.Size:]
 | |
| 		//verify signature
 | |
| 		mac := hmac.New(sha256.New, []byte(cfg.CookieSecret))
 | |
| 		mac.Write([]byte(cookieName))
 | |
| 		mac.Write([]byte(sessionToken))
 | |
| 		expectedSignature := mac.Sum(nil)
 | |
| 		if !hmac.Equal([]byte(signature), expectedSignature) {
 | |
| 			log.Debug("cookie with an invalid sign")
 | |
| 			next.ServeHTTP(w, r)
 | |
| 			return
 | |
| 		}
 | |
| 		userSession, err := cacheGetSession(sessionToken)
 | |
| 		if err != nil {
 | |
| 			msg := "auth failed; session does not exists"
 | |
| 			err = errors.New(msg)
 | |
| 			log.Debug(msg, "error", err)
 | |
| 			next.ServeHTTP(w, r)
 | |
| 			return
 | |
| 		}
 | |
| 		if userSession.IsExpired() {
 | |
| 			memcache.RemoveKey(sessionToken)
 | |
| 			msg := "session is expired"
 | |
| 			log.Debug(msg, "error", err, "token", sessionToken)
 | |
| 			next.ServeHTTP(w, r)
 | |
| 			return
 | |
| 		}
 | |
| 		ctx := context.WithValue(r.Context(),
 | |
| 			models.CtxUsernameKey, userSession.Username)
 | |
| 		ctx = context.WithValue(r.Context(),
 | |
| 			models.CtxSessionKey, userSession)
 | |
| 		if err := cacheSetSession(sessionToken,
 | |
| 			userSession); err != nil {
 | |
| 			msg := "failed to marshal user session"
 | |
| 			log.Warn(msg, "error", err)
 | |
| 			next.ServeHTTP(w, r)
 | |
| 			return
 | |
| 		}
 | |
| 		next.ServeHTTP(w, r.WithContext(ctx))
 | |
| 	})
 | |
| }
 | 
