98 lines
2.2 KiB
Go
98 lines
2.2 KiB
Go
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
|
|
}
|