draincloud-core/internal/resolvers/auth/auth_resolver.go

96 lines
2.3 KiB
Go

package auth
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"time"
"git.optclblast.xyz/draincloud/draincloud-core/internal/common"
"git.optclblast.xyz/draincloud/draincloud-core/internal/errs"
"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage"
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
)
const (
csrfTokenCookie = "__Csrf_token"
sessionTokenCookie = "__Session_token"
)
type AuthResolver struct {
authStorage storage.AuthStorage
}
func (r *AuthResolver) Resolve(ctx context.Context, req *common.Request) error {
return r.authorize(ctx, req)
}
func (p *AuthResolver) authorize(ctx context.Context, req *common.Request) error {
session, err := p.getSession(ctx, req)
if err != nil && !errors.Is(err, http.ErrNoCookie) {
return errs.ErrorUnauthorized
}
if session == nil {
return errs.ErrorUnauthorized
}
if err := validateSession(ctx, req, session); err != nil {
// TODO add audit log entry
return errs.ErrorUnauthorized
}
user, err := p.authStorage.GetUserByID(ctx, session.UserID)
if err != nil {
return fmt.Errorf("failed to fetch user by id: %w", err)
}
logger.Debug(ctx, "[authorize] user authorized", slog.String("session_id", session.ID.String()))
req.User = user
req.Session = session
return nil
}
func (d *AuthResolver) getSession(ctx context.Context, req *common.Request) (*models.Session, error) {
token, err := common.GetValue[string](req.Metadata, sessionTokenCookie)
if err != nil {
return nil, fmt.Errorf("failed to fetch session cookie from request: %w", err)
}
if len(token) == 0 {
return nil, fmt.Errorf("session token or csrf token is empty")
}
session, err := d.authStorage.GetSession(ctx, token)
if err != nil {
return nil, fmt.Errorf("failed to fetch session from repo: %w", err)
}
return session, nil
}
func validateSession(_ context.Context, req *common.Request, session *models.Session) error {
if session == nil {
return errs.ErrorAccessDenied
}
csrfToken, err := common.GetValue[string](req.Metadata, csrfTokenCookie)
if err != nil {
return fmt.Errorf("failed to fetch csrf cookie from request: %w", err)
}
if session.CsrfToken != csrfToken {
return errs.ErrorAccessDenied
}
if session.ExpiredAt.Before(time.Now()) {
return errs.ErrorSessionExpired
}
return nil
}