package auth import ( "context" "database/sql" "net" "net/http" "time" "git.noroutes.net/NoRoutes/vibetunes/internal/database" ) const ( SessionCookieName = "vibesonic_session" SessionTTL = 30 * 24 * time.Hour ) // Sessions issues and validates cookie sessions backed by the sessions // table, so they can be listed and revoked server-side. type Sessions struct { DB *sql.DB CookieSecure bool } func (s *Sessions) Issue(w http.ResponseWriter, r *http.Request, userID string) error { sess, err := database.CreateSession(s.DB, userID, SessionTTL, r.UserAgent(), clientIP(r)) if err != nil { return err } http.SetCookie(w, &http.Cookie{ Name: SessionCookieName, Value: sess.ID, Path: "/", HttpOnly: true, Secure: s.CookieSecure, SameSite: http.SameSiteLaxMode, Expires: sess.ExpiresAt, }) return nil } func (s *Sessions) Clear(w http.ResponseWriter, r *http.Request) { if c, err := r.Cookie(SessionCookieName); err == nil { _ = database.DeleteSession(s.DB, c.Value) } http.SetCookie(w, &http.Cookie{ Name: SessionCookieName, Value: "", Path: "/", MaxAge: -1, HttpOnly: true, Secure: s.CookieSecure, }) } // CurrentUser resolves the authenticated user from the session cookie, or // nil if there is none / it is invalid / expired / the user is disabled. func (s *Sessions) CurrentUser(r *http.Request) *database.User { c, err := r.Cookie(SessionCookieName) if err != nil { return nil } sess, err := database.GetSession(s.DB, c.Value) if err != nil { return nil } if time.Now().After(sess.ExpiresAt) { _ = database.DeleteSession(s.DB, sess.ID) return nil } user, err := database.GetUserByID(s.DB, sess.UserID) if err != nil || user.Disabled { return nil } return user } func clientIP(r *http.Request) string { host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return r.RemoteAddr } return host } type contextKey string const userContextKey contextKey = "vibesonic_user" func WithUser(ctx context.Context, u *database.User) context.Context { return context.WithValue(ctx, userContextKey, u) } func UserFromContext(ctx context.Context) *database.User { u, _ := ctx.Value(userContextKey).(*database.User) return u }