96 lines
2.3 KiB
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
|
|
}
|