updated stuff

This commit is contained in:
2026-01-23 19:07:05 +11:00
parent c14c5d43ee
commit b810b75011
33 changed files with 1186 additions and 222 deletions

View File

@@ -1,15 +1,21 @@
package handlers
import (
"context"
"net/http"
"time"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/oslstats/internal/config"
"git.haelnorr.com/h/oslstats/internal/db"
"git.haelnorr.com/h/oslstats/internal/discord"
"git.haelnorr.com/h/oslstats/internal/session"
"git.haelnorr.com/h/oslstats/pkg/oauth"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
func Callback(server *hws.Server, cfg *config.Config) http.Handler {
func Callback(server *hws.Server, conn *bun.DB, cfg *config.Config, store *session.Store) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
state := r.URL.Query().Get("state")
@@ -20,42 +26,141 @@ func Callback(server *hws.Server, cfg *config.Config) http.Handler {
}
data, err := verifyState(cfg.OAuth, w, r, state)
if err != nil {
err = server.ThrowError(w, r, hws.HWSError{
StatusCode: http.StatusForbidden,
Message: "OAuth state verification failed",
Error: err,
Level: hws.ErrorLevel("debug"),
RenderErrorPage: true,
})
if err != nil {
server.ThrowFatal(w, err)
// Check if this is a cookie error (401) or signature error (403)
if vsErr, ok := err.(*verifyStateError); ok {
if vsErr.IsCookieError() {
// Cookie missing/expired - normal failed/expired session (DEBUG)
throwUnauthorized(server, w, r, "OAuth session not found or expired", err)
} else {
// Signature verification failed - security violation (WARN)
throwForbiddenSecurity(server, w, r, "OAuth state verification failed", err)
}
} else {
// Unknown error type - treat as security issue
throwForbiddenSecurity(server, w, r, "OAuth state verification failed", err)
}
return
}
switch data {
case "login":
w.Write([]byte(code))
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
throwInternalServiceError(server, w, r, "DB Transaction failed to start", err)
return
}
defer tx.Rollback()
redirect, err := login(ctx, tx, cfg, w, r, code, store)
if err != nil {
throwInternalServiceError(server, w, r, "OAuth login failed", err)
return
}
tx.Commit()
redirect()
return
}
},
)
}
func verifyState(cfg *oauth.Config, w http.ResponseWriter, r *http.Request, state string) (string, error) {
// verifyStateError wraps an error with context about what went wrong
type verifyStateError struct {
err error
cookieError bool // true if cookie missing/invalid, false if signature invalid
}
func (e *verifyStateError) Error() string {
return e.err.Error()
}
func (e *verifyStateError) IsCookieError() bool {
return e.cookieError
}
func verifyState(
cfg *oauth.Config,
w http.ResponseWriter,
r *http.Request,
state string,
) (string, error) {
if r == nil {
return "", errors.New("request cannot be nil")
}
if state == "" {
return "", errors.New("state param field is empty")
}
// Try to get the cookie
uak, err := oauth.GetStateCookie(r)
if err != nil {
return "", errors.Wrap(err, "oauth.GetStateCookie")
// Cookie missing or invalid - this is a 401 (not authenticated)
return "", &verifyStateError{
err: errors.Wrap(err, "oauth.GetStateCookie"),
cookieError: true,
}
}
// Verify the state signature
data, err := oauth.VerifyState(cfg, state, uak)
if err != nil {
return "", errors.Wrap(err, "oauth.VerifyState")
// Signature verification failed - this is a 403 (security violation)
return "", &verifyStateError{
err: errors.Wrap(err, "oauth.VerifyState"),
cookieError: false,
}
}
oauth.DeleteStateCookie(w)
return data, nil
}
func login(
ctx context.Context,
tx bun.Tx,
cfg *config.Config,
w http.ResponseWriter,
r *http.Request,
code string,
store *session.Store,
) (func(), error) {
token, err := discord.AuthorizeWithCode(cfg.Discord, code, cfg.HWSAuth.TrustedHost)
if err != nil {
return nil, errors.Wrap(err, "discord.AuthorizeWithCode")
}
session, err := discord.NewOAuthSession(token)
if err != nil {
return nil, errors.Wrap(err, "discord.NewOAuthSession")
}
discorduser, err := session.GetUser()
if err != nil {
return nil, errors.Wrap(err, "session.GetUser")
}
user, err := db.GetUserByDiscordID(ctx, tx, discorduser.ID)
if err != nil {
return nil, errors.Wrap(err, "db.GetUserByDiscordID")
}
var redirect string
if user == nil {
sessionID, err := store.CreateRegistrationSession(discorduser, token)
if err != nil {
return nil, errors.Wrap(err, "store.CreateRegistrationSession")
}
http.SetCookie(w, &http.Cookie{
Name: "registration_session",
Path: "/",
Value: sessionID,
MaxAge: 300, // 5 minutes
HttpOnly: true,
Secure: cfg.HWSAuth.SSL,
SameSite: http.SameSiteLaxMode,
})
redirect = "/register"
} else {
// TODO: log them in
}
return func() {
http.Redirect(w, r, redirect, http.StatusSeeOther)
}, nil
}