Files
upstream-traggo/auth/middleware.go
upstream-mirror cda7c1a474
Some checks failed
build / build (push) Failing after 3s
snapshot of https://github.com/traggo/server.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 05:24:45 +05:00

183 lines
5.2 KiB
Go

package auth
import (
"context"
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
"github.com/rs/zerolog/log"
"github.com/traggo/server/model"
)
type key string
var (
traggoSession = "traggo"
createSessionKey key = "createSession"
destroySessionKey key = "destroySession"
userKey key = "user"
deviceKey key = "device"
)
// Middleware is the auth middleware which sets user and device context parameters.
func Middleware(db *gorm.DB) mux.MiddlewareFunc {
return func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
handler.ServeHTTP(writer, sessionCallbacks(reqisterUser(request, writer, db), writer))
})
}
}
func sessionCallbacks(request *http.Request, writer http.ResponseWriter) *http.Request {
createSession := func(token string, maxAge int) {
http.SetCookie(writer, &http.Cookie{
Name: traggoSession,
Value: token,
MaxAge: maxAge,
})
}
destroySession := func() {
http.SetCookie(writer, &http.Cookie{
Name: traggoSession,
Value: "",
MaxAge: -1,
})
}
return request.WithContext(WithDestroySession(WithCreateSession(request.Context(), createSession), destroySession))
}
func reqisterUser(request *http.Request, writer http.ResponseWriter, db *gorm.DB) *http.Request {
token, err := getToken(request)
if err != nil {
return request
}
device := &model.Device{}
if db.Where("token = ?", token).Find(device).RecordNotFound() {
log.Debug().Str("token", token).Msg("No device with token found")
return request
}
user := &model.User{}
if db.Find(user, device.UserID).RecordNotFound() {
log.Panic().Int("userID", device.UserID).Int("deviceID", device.ID).Msg("User not found")
}
log.Debug().Int("userid", device.UserID).Str("username", user.Name).Msg("User found")
impersonate := request.Header.Get("X-Traggo-Impersonate")
if impersonate != "" {
if !user.Admin {
log.Info().Str("impersonate", impersonate).Msg("Trying to impersonate without being admin")
return request
}
userID, err := strconv.Atoi(impersonate)
if err != nil {
log.Info().Str("impersonate", impersonate).Msg("Unable to parse impersonation header")
return request
}
impersonateUser := &model.User{}
if db.Find(impersonateUser, userID).RecordNotFound() {
log.Info().Int("userid", userID).Msg("Impersonation user not found")
return request
}
user = impersonateUser
log.Info().Str("username", user.Name).Msg("Impersonation user")
}
if device.ActiveAt.Before(time.Now().Add(5 * -time.Minute)) {
log.Debug().Int("deviceId", device.ID).Str("deviceName", device.Name).Msg("update device activeAt")
device.ActiveAt = timeNow()
db.Save(device)
if cookie, err := request.Cookie(traggoSession); err == nil && cookie != nil {
cookie.MaxAge = device.Type.Seconds()
http.SetCookie(writer, cookie)
}
}
return request.WithContext(WithUser(WithDevice(request.Context(), device), user))
}
func getToken(request *http.Request) (string, error) {
if value := request.Header.Get("Authorization"); value != "" && strings.HasPrefix(value, "traggo ") {
log.Debug().Msg("Using authorization header for authentication")
key := strings.TrimPrefix(value, "traggo ")
return key, nil
}
if cookie, err := request.Cookie("traggo"); err == nil && cookie != nil && cookie.Value != "" {
log.Debug().Msg("Using cookie for authentication")
return cookie.Value, nil
}
if token := request.FormValue("token"); token != "" {
log.Debug().Msg("Using query parameter for authentication")
return token, nil
}
return "", errors.New("no token found")
}
// WithDevice adds the authenticated device to the context.
func WithDevice(ctx context.Context, device *model.Device) context.Context {
return context.WithValue(
ctx,
deviceKey,
device)
}
// WithUser adds the authenticated user to the context.
func WithUser(ctx context.Context, user *model.User) context.Context {
return context.WithValue(
ctx,
userKey,
user)
}
// WithCreateSession adds the create session method
func WithCreateSession(ctx context.Context, f func(token string, age int)) context.Context {
return context.WithValue(ctx, createSessionKey, f)
}
// WithDestroySession adds the destroy session method
func WithDestroySession(ctx context.Context, f func()) context.Context {
return context.WithValue(ctx, destroySessionKey, f)
}
// GetUser returns the authenticated user or panics.
func GetUser(ctx context.Context) *model.User {
if user, ok := ctx.Value(userKey).(*model.User); ok {
return user
}
return nil
}
// GetDevice returns the authenticated device or nil.
func GetDevice(ctx context.Context) *model.Device {
if device, ok := ctx.Value(deviceKey).(*model.Device); ok {
return device
}
return nil
}
// GetCreateSession gets the create session callback
func GetCreateSession(ctx context.Context) func(token string, age int) {
if f, ok := ctx.Value(createSessionKey).(func(string, int)); ok {
return f
}
panic("create session must exist")
}
// GetDestroySession gets the destroy session callback
func GetDestroySession(ctx context.Context) func() {
if f, ok := ctx.Value(destroySessionKey).(func()); ok {
return f
}
panic("create session must exist")
}