dbots/services/auth/router.go

153 lines
3.5 KiB
Go

package auth
import (
"net/http"
"time"
"codeberg.org/nextgo/dbots/internal/config"
"codeberg.org/nextgo/dbots/internal/db"
"codeberg.org/nextgo/dbots/internal/discord"
"codeberg.org/nextgo/dbots/internal/errorutil"
"codeberg.org/nextgo/dbots/internal/middleware"
"codeberg.org/nextgo/dbots/internal/token"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)
const cookieName = "session"
type Router struct {
auth *Service
router chi.Router
config config.Config
queries *db.Queries
}
func NewRouter(q *db.Queries, client *discord.Client, config config.Config) *Router {
return &Router{
auth: NewService(q, client),
router: chi.NewRouter(),
config: config,
queries: q,
}
}
func (r *Router) Routes() http.Handler {
r.router.Get("/login", r.login)
r.router.Get("/callback", r.callback)
r.router.Post("/logout", r.logout)
r.router.With(middleware.AuthGuardMiddleware).Get("/me", r.me)
return r.router
}
func (r *Router) me(w http.ResponseWriter, req *http.Request) {
user := middleware.GetUser(req.Context())
if user == nil {
render.Render(w, req, errorutil.ErrUnauthorized)
return
}
render.JSON(w, req, user)
}
func (r *Router) login(w http.ResponseWriter, req *http.Request) {
state, err := GenerateState()
if err != nil {
render.Render(w, req, errorutil.ErrInternal(err))
return
}
http.SetCookie(w, &http.Cookie{
Name: "oauth_state",
Value: state,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
MaxAge: 300,
Path: "/",
})
http.Redirect(w, req, r.auth.client.AuthURL(state), http.StatusFound)
}
func (r *Router) callback(w http.ResponseWriter, req *http.Request) {
stateCookie, err := req.Cookie("oauth_state")
if err != nil || stateCookie.Value != req.URL.Query().Get("state") {
render.Render(w, req, errorutil.ErrUnauthorized)
return
}
http.SetCookie(w, &http.Cookie{Name: "oauth_state", MaxAge: -1, Path: "/"})
code := req.URL.Query().Get("code")
if code == "" {
render.Render(w, req, errorutil.ErrInvalidRequest(nil))
return
}
user, _, err := r.auth.Callback(req.Context(), code)
if err != nil {
render.Render(w, req, errorutil.ErrInternal(err))
return
}
jti, err := GenerateState()
if err != nil {
render.Render(w, req, errorutil.ErrInternal(err))
return
}
_, err = r.queries.CreateSession(req.Context(), db.CreateSessionParams{
ID: jti,
UserID: user.ID,
ExpiresAt: time.Now().Add(token.TokenDuration),
})
if err != nil {
render.Render(w, req, errorutil.ErrInternal(err))
return
}
key, err := token.KeyFromHex(r.config.Auth.PasetoKey)
if err != nil {
render.Render(w, req, errorutil.ErrInternal(err))
return
}
raw, err := token.IssueToken(key, user.ID, jti)
if err != nil {
render.Render(w, req, errorutil.ErrInternal(err))
return
}
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: raw,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
MaxAge: int(token.TokenDuration.Seconds()),
Path: "/",
Secure: r.config.Production,
})
http.Redirect(w, req, "/", http.StatusFound)
}
func (r *Router) logout(w http.ResponseWriter, req *http.Request) {
c, err := req.Cookie(cookieName)
if err != nil {
render.NoContent(w, req)
return
}
key, err := token.KeyFromHex(r.config.Auth.PasetoKey)
if err == nil {
if claims, err := token.VerifyToken(key, c.Value); err == nil {
_ = r.queries.RevokeSession(req.Context(), claims.JTI)
}
}
http.SetCookie(w, &http.Cookie{
Name: cookieName,
MaxAge: -1,
Path: "/",
})
render.NoContent(w, req)
}