dbots/internal/middleware/auth.go

74 lines
2 KiB
Go

package middleware
import (
"context"
"net/http"
"git.elisiei.xyz/elisiei/dbots/internal/db"
"git.elisiei.xyz/elisiei/dbots/internal/errorutil"
"git.elisiei.xyz/elisiei/dbots/internal/token"
"github.com/go-chi/render"
)
type contextKey string
var UserContextKey contextKey = "user"
// AuthMiddleware reads the PASETO session cookie, verifies it, checks it
// hasn't been revoked in the db, then sets the *db.User on the context.
// does not block unauthenticated requests
func AuthMiddleware(q *db.Queries, pasetoKeyHex string) func(http.Handler) http.Handler {
key, err := token.KeyFromHex(pasetoKeyHex)
if err != nil {
panic("middleware: invalid PASETO key: " + err.Error())
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie("session")
if err != nil {
next.ServeHTTP(w, r)
return
}
claims, err := token.VerifyToken(key, c.Value)
if err != nil {
http.SetCookie(w, &http.Cookie{Name: "session", MaxAge: -1, Path: "/"})
next.ServeHTTP(w, r)
return
}
if _, err := q.GetSession(r.Context(), claims.JTI); err != nil {
http.SetCookie(w, &http.Cookie{Name: "session", MaxAge: -1, Path: "/"})
next.ServeHTTP(w, r)
return
}
user, err := q.GetUser(r.Context(), claims.UserID)
if err != nil {
next.ServeHTTP(w, r)
return
}
ctx := context.WithValue(r.Context(), UserContextKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// AuthGuardMiddleware blocks requests where no authenticated user was set.
func AuthGuardMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if _, ok := r.Context().Value(UserContextKey).(*db.User); !ok {
render.Render(w, r, errorutil.ErrUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// GetUser returns the authenticated user from context.
// Only safe to call inside a route guarded by AuthGuardMiddleware.
func GetUser(ctx context.Context) *db.User {
return ctx.Value(UserContextKey).(*db.User)
}