Foundation: config, schema/migrations, auth, stable IDs, metadata probing

This commit is contained in:
2026-08-22 23:29:16 -05:00
commit d4ea7a4ca9
40 changed files with 3874 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
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
}