-
This commit is contained in:
@ -49,39 +49,46 @@ func (hv *HeaderValidator) ValidateAuthenticationHeaders(r *http.Request) (*Vali
|
||||
return nil, errors.NewAuthenticationError("User authentication required")
|
||||
}
|
||||
|
||||
if timestamp == "" || signature == "" {
|
||||
hv.logger.Warn("Missing authentication signature headers",
|
||||
// In development mode, skip signature validation for trusted headers
|
||||
if hv.config.IsDevelopment() {
|
||||
hv.logger.Debug("Development mode: skipping signature validation",
|
||||
zap.String("user_email", userEmail))
|
||||
return nil, errors.NewAuthenticationError("Authentication signature required")
|
||||
}
|
||||
} else {
|
||||
// Production mode: require full signature validation
|
||||
if timestamp == "" || signature == "" {
|
||||
hv.logger.Warn("Missing authentication signature headers",
|
||||
zap.String("user_email", userEmail))
|
||||
return nil, errors.NewAuthenticationError("Authentication signature required")
|
||||
}
|
||||
|
||||
// Validate timestamp (prevent replay attacks)
|
||||
timestampInt, err := strconv.ParseInt(timestamp, 10, 64)
|
||||
if err != nil {
|
||||
hv.logger.Warn("Invalid timestamp format",
|
||||
zap.String("timestamp", timestamp),
|
||||
zap.String("user_email", userEmail))
|
||||
return nil, errors.NewAuthenticationError("Invalid timestamp format")
|
||||
}
|
||||
// Validate timestamp (prevent replay attacks)
|
||||
timestampInt, err := strconv.ParseInt(timestamp, 10, 64)
|
||||
if err != nil {
|
||||
hv.logger.Warn("Invalid timestamp format",
|
||||
zap.String("timestamp", timestamp),
|
||||
zap.String("user_email", userEmail))
|
||||
return nil, errors.NewAuthenticationError("Invalid timestamp format")
|
||||
}
|
||||
|
||||
timestampTime := time.Unix(timestampInt, 0)
|
||||
now := time.Now()
|
||||
|
||||
// Allow 5 minutes clock skew
|
||||
maxAge := 5 * time.Minute
|
||||
if now.Sub(timestampTime) > maxAge || timestampTime.After(now.Add(1*time.Minute)) {
|
||||
hv.logger.Warn("Timestamp outside acceptable window",
|
||||
zap.Time("timestamp", timestampTime),
|
||||
zap.Time("now", now),
|
||||
zap.String("user_email", userEmail))
|
||||
return nil, errors.NewAuthenticationError("Request timestamp outside acceptable window")
|
||||
}
|
||||
timestampTime := time.Unix(timestampInt, 0)
|
||||
now := time.Now()
|
||||
|
||||
// Allow 5 minutes clock skew
|
||||
maxAge := 5 * time.Minute
|
||||
if now.Sub(timestampTime) > maxAge || timestampTime.After(now.Add(1*time.Minute)) {
|
||||
hv.logger.Warn("Timestamp outside acceptable window",
|
||||
zap.Time("timestamp", timestampTime),
|
||||
zap.Time("now", now),
|
||||
zap.String("user_email", userEmail))
|
||||
return nil, errors.NewAuthenticationError("Request timestamp outside acceptable window")
|
||||
}
|
||||
|
||||
// Validate HMAC signature
|
||||
if !hv.validateSignature(userEmail, timestamp, signature) {
|
||||
hv.logger.Warn("Invalid authentication signature",
|
||||
zap.String("user_email", userEmail))
|
||||
return nil, errors.NewAuthenticationError("Invalid authentication signature")
|
||||
// Validate HMAC signature
|
||||
if !hv.validateSignature(userEmail, timestamp, signature) {
|
||||
hv.logger.Warn("Invalid authentication signature",
|
||||
zap.String("user_email", userEmail))
|
||||
return nil, errors.NewAuthenticationError("Invalid authentication signature")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate email format
|
||||
@ -94,11 +101,24 @@ func (hv *HeaderValidator) ValidateAuthenticationHeaders(r *http.Request) (*Vali
|
||||
hv.logger.Debug("Authentication headers validated successfully",
|
||||
zap.String("user_email", userEmail))
|
||||
|
||||
// Set defaults for development mode
|
||||
var timestampTime time.Time
|
||||
var signatureValue string
|
||||
|
||||
if hv.config.IsDevelopment() {
|
||||
timestampTime = time.Now()
|
||||
signatureValue = "dev-mode-bypass"
|
||||
} else {
|
||||
timestampInt, _ := strconv.ParseInt(timestamp, 10, 64)
|
||||
timestampTime = time.Unix(timestampInt, 0)
|
||||
signatureValue = signature
|
||||
}
|
||||
|
||||
return &ValidatedUserContext{
|
||||
UserID: userEmail,
|
||||
Email: userEmail,
|
||||
Timestamp: timestampTime,
|
||||
Signature: signature,
|
||||
Signature: signatureValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@ -124,11 +124,20 @@ type VerifyResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// TokenDeliveryMode specifies how tokens should be delivered in redirect flows
|
||||
type TokenDeliveryMode string
|
||||
|
||||
const (
|
||||
TokenDeliveryCookie TokenDeliveryMode = "cookie" // Token in secure cookie (default)
|
||||
TokenDeliveryQuery TokenDeliveryMode = "query" // Token in query parameter (for integrations)
|
||||
)
|
||||
|
||||
// LoginRequest represents a user login request
|
||||
type LoginRequest struct {
|
||||
AppID string `json:"app_id" validate:"required"`
|
||||
Permissions []string `json:"permissions,omitempty"`
|
||||
RedirectURI string `json:"redirect_uri,omitempty"`
|
||||
AppID string `json:"app_id" validate:"required"`
|
||||
Permissions []string `json:"permissions,omitempty"`
|
||||
RedirectURI string `json:"redirect_uri,omitempty"`
|
||||
TokenDelivery TokenDeliveryMode `json:"token_delivery,omitempty"` // How to deliver token in redirect flows
|
||||
}
|
||||
|
||||
// LoginResponse represents a user login response
|
||||
|
||||
@ -81,25 +81,53 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// For redirect flows, use secure cookie-based token delivery
|
||||
// Set secure cookie with the token
|
||||
c.SetSameSite(http.SameSiteStrictMode)
|
||||
c.SetCookie(
|
||||
"auth_token", // name
|
||||
token, // value
|
||||
604800, // maxAge (7 days)
|
||||
"/", // path
|
||||
"", // domain (empty for current domain)
|
||||
true, // secure (HTTPS only)
|
||||
true, // httpOnly (no JavaScript access)
|
||||
)
|
||||
// For redirect flows, choose token delivery method
|
||||
// Default to cookie delivery for security
|
||||
tokenDelivery := req.TokenDelivery
|
||||
if tokenDelivery == "" {
|
||||
tokenDelivery = domain.TokenDeliveryCookie
|
||||
}
|
||||
|
||||
h.logger.Debug("Token delivery mode", zap.String("mode", string(tokenDelivery)))
|
||||
|
||||
// Generate a secure state parameter for CSRF protection
|
||||
state := h.generateSecureState(userContext.UserID, req.AppID)
|
||||
|
||||
// Redirect without token in URL
|
||||
var redirectURL string
|
||||
|
||||
switch tokenDelivery {
|
||||
case domain.TokenDeliveryQuery:
|
||||
// Deliver token via query parameter (for integrations like VS Code)
|
||||
redirectURL = req.RedirectURI + "?token=" + token + "&state=" + state
|
||||
|
||||
case domain.TokenDeliveryCookie:
|
||||
// Deliver token via secure cookie (default, more secure)
|
||||
c.SetSameSite(http.SameSiteStrictMode)
|
||||
|
||||
// In development mode, make cookie accessible to JavaScript for testing
|
||||
// In production, keep HTTP-only for security
|
||||
httpOnly := !h.config.IsDevelopment()
|
||||
secure := !h.config.IsDevelopment() // Only require HTTPS in production
|
||||
|
||||
c.SetCookie(
|
||||
"auth_token", // name
|
||||
token, // value
|
||||
604800, // maxAge (7 days)
|
||||
"/", // path
|
||||
"", // domain (empty for current domain)
|
||||
secure, // secure (HTTPS only in production)
|
||||
httpOnly, // httpOnly (no JavaScript access in production)
|
||||
)
|
||||
|
||||
// Redirect without token in URL for security
|
||||
redirectURL = req.RedirectURI + "?state=" + state
|
||||
|
||||
default:
|
||||
// Invalid delivery mode, default to cookie
|
||||
redirectURL = req.RedirectURI + "?state=" + state
|
||||
}
|
||||
|
||||
response := domain.LoginResponse{
|
||||
RedirectURL: req.RedirectURI + "?state=" + state,
|
||||
RedirectURL: redirectURL,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
|
||||
Reference in New Issue
Block a user