Foundation: config, schema/migrations, auth, stable IDs, metadata probing
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user