package middleware import "context" import "net/http" import "strings" import "time" import "codit/internal/db" import "codit/internal/models" import "codit/internal/util" type ctxKey string const userKey ctxKey = "user" const principalKey ctxKey = "principal" func WithUser(store *db.Store, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var cookie *http.Cookie var err error var user models.User var expires time.Time var ctx context.Context var token string var hash string cookie, err = r.Cookie("codit_session") if err != nil || cookie.Value == "" { token = apiKeyFromRequest(r) if token == "" { next.ServeHTTP(w, r) return } hash = util.HashToken(token) user, err = store.GetUserByAPIKeyHash(hash) if err != nil { next.ServeHTTP(w, r) return } ctx = context.WithValue(r.Context(), userKey, user) next.ServeHTTP(w, r.WithContext(ctx)) return } user, expires, err = store.GetSessionUser(cookie.Value) if err != nil || time.Now().UTC().After(expires) { token = apiKeyFromRequest(r) if token == "" { next.ServeHTTP(w, r) return } hash = util.HashToken(token) user, err = store.GetUserByAPIKeyHash(hash) if err != nil { next.ServeHTTP(w, r) return } ctx = context.WithValue(r.Context(), userKey, user) next.ServeHTTP(w, r.WithContext(ctx)) return } ctx = context.WithValue(r.Context(), userKey, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } func UserFromContext(ctx context.Context) (models.User, bool) { var user models.User var ok bool user, ok = ctx.Value(userKey).(models.User) return user, ok } func WithPrincipal(r *http.Request, principal models.ServicePrincipal) *http.Request { var ctx context.Context ctx = context.WithValue(r.Context(), principalKey, principal) return r.WithContext(ctx) } func PrincipalFromContext(ctx context.Context) (models.ServicePrincipal, bool) { var principal models.ServicePrincipal var ok bool principal, ok = ctx.Value(principalKey).(models.ServicePrincipal) return principal, ok } func RequireAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var ok bool _, ok = UserFromContext(r.Context()) if !ok { w.WriteHeader(http.StatusUnauthorized) return } next.ServeHTTP(w, r) }) } func RequireAdmin(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var user models.User var ok bool user, ok = UserFromContext(r.Context()) if !ok || !user.IsAdmin { w.WriteHeader(http.StatusForbidden) return } next.ServeHTTP(w, r) }) } func apiKeyFromRequest(r *http.Request) string { var token string var auth string var parts []string token = r.Header.Get("X-API-Key") if token != "" { return token } auth = r.Header.Get("Authorization") if auth == "" { return "" } parts = strings.SplitN(auth, " ", 2) if len(parts) != 2 { return "" } if strings.ToLower(parts[0]) != "bearer" { return "" } return parts[1] }