updated stuff
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user