github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/app/controller/cutil/session.go (about)

     1  // Package cutil - Content managed by Project Forge, see [projectforge.md] for details.
     2  package cutil
     3  
     4  import (
     5  	"bytes"
     6  	"context"
     7  	"io"
     8  	"net/http"
     9  	"slices"
    10  	"strings"
    11  
    12  	"github.com/mileusna/useragent"
    13  
    14  	"github.com/kyleu/dbaudit/app"
    15  	"github.com/kyleu/dbaudit/app/controller/csession"
    16  	"github.com/kyleu/dbaudit/app/lib/telemetry"
    17  	"github.com/kyleu/dbaudit/app/lib/telemetry/httpmetrics"
    18  	"github.com/kyleu/dbaudit/app/lib/user"
    19  	"github.com/kyleu/dbaudit/app/util"
    20  )
    21  
    22  var (
    23  	initialIcons = []string{"searchbox"}
    24  	MaxBodySize  = int64(1024 * 1024 * 128) // 128MB
    25  )
    26  
    27  func LoadPageState(as *app.State, w http.ResponseWriter, r *http.Request, key string, logger util.Logger) *PageState {
    28  	parentCtx, logger := httpmetrics.ExtractHeaders(r, logger)
    29  	ctx, span, logger := telemetry.StartSpan(parentCtx, "http:"+key, logger)
    30  	span.Attribute("path", r.URL.Path)
    31  	if !telemetry.SkipControllerMetrics {
    32  		httpmetrics.InjectHTTP(200, r, span)
    33  	}
    34  	session, flashes, prof, accts := loadSession(ctx, as, w, r, logger)
    35  	params := ParamSetFromRequest(r)
    36  	ua := useragent.Parse(r.Header.Get("User-Agent"))
    37  	os := strings.ToLower(ua.OS)
    38  	browser := strings.ToLower(ua.Name)
    39  	platform := "unknown"
    40  	switch {
    41  	case ua.Desktop:
    42  		platform = "desktop"
    43  	case ua.Tablet:
    44  		platform = "tablet"
    45  	case ua.Mobile:
    46  		platform = "mobile"
    47  	case ua.Bot:
    48  		platform = "bot"
    49  	}
    50  	span.Attribute("browser", browser)
    51  	span.Attribute("os", os)
    52  
    53  	isAuthed, _ := user.Check("/", accts)
    54  	isAdmin, _ := user.Check("/admin", accts)
    55  	b, _ := io.ReadAll(http.MaxBytesReader(w, r.Body, MaxBodySize))
    56  	r.Body = io.NopCloser(bytes.NewBuffer(b))
    57  
    58  	return &PageState{
    59  		Action: key, Method: r.Method, URI: r.URL, Flashes: flashes, Session: session,
    60  		OS: os, OSVersion: ua.OSVersion, Browser: browser, BrowserVersion: ua.Version, Platform: platform,
    61  		Profile: prof, Accounts: accts, Authed: isAuthed, Admin: isAdmin, Params: params,
    62  		Icons: slices.Clone(initialIcons), Started: util.TimeCurrent(), Logger: logger, Context: ctx, Span: span, RequestBody: b,
    63  	}
    64  }
    65  
    66  func loadSession(_ context.Context, _ *app.State, w http.ResponseWriter, r *http.Request, logger util.Logger) (util.ValueMap, []string, *user.Profile, user.Accounts) {
    67  	c, _ := r.Cookie(util.AppKey)
    68  	if c == nil || c.Value == "" {
    69  		return util.ValueMap{}, nil, user.DefaultProfile.Clone(), nil
    70  	}
    71  
    72  	dec, err := util.DecryptMessage(nil, c.Value, logger)
    73  	if err != nil {
    74  		logger.Warnf("error decrypting session: %+v", err)
    75  	}
    76  	session, err := util.FromJSONMap([]byte(dec))
    77  	if err != nil {
    78  		session = util.ValueMap{}
    79  	}
    80  
    81  	flashes := util.StringSplitAndTrim(session.GetStringOpt(csession.WebFlashKey), ";")
    82  	if len(flashes) > 0 {
    83  		delete(session, csession.WebFlashKey)
    84  		err := csession.SaveSession(w, session, logger)
    85  		if err != nil {
    86  			logger.Warnf("can't save session: %+v", err)
    87  		}
    88  	}
    89  
    90  	prof, err := loadProfile(session)
    91  	if err != nil {
    92  		logger.Warnf("can't load profile: %+v", err)
    93  	}
    94  
    95  	var accts user.Accounts
    96  	authX, ok := session[csession.WebAuthKey]
    97  	if ok {
    98  		authS, ok := authX.(string)
    99  		if ok {
   100  			accts = user.AccountsFromString(authS)
   101  		}
   102  	}
   103  
   104  	return session, flashes, prof, accts
   105  }
   106  
   107  func loadProfile(session util.ValueMap) (*user.Profile, error) {
   108  	x, ok := session["profile"]
   109  	if !ok {
   110  		return user.DefaultProfile.Clone(), nil
   111  	}
   112  	s, ok := x.(string)
   113  	if !ok {
   114  		m, ok := x.(map[string]any)
   115  		if !ok {
   116  			return user.DefaultProfile.Clone(), nil
   117  		}
   118  		s = util.ToJSON(m)
   119  	}
   120  	p := &user.Profile{}
   121  	err := util.FromJSON([]byte(s), p)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	if p.Name == "" {
   126  		p.Name = user.DefaultProfile.Name
   127  	}
   128  	return p, nil
   129  }