# Know Foolery - Security Requirements & Implementation
## Security Overview
Know Foolery implements a comprehensive security strategy that addresses authentication, authorization, data protection, application security, and compliance requirements. This document outlines security measures implemented across all system components.
## Security Architecture
### Defense in Depth Strategy
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Security Layers │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Perimeter Security │ │
│ │ WAF • DDoS Protection • Rate Limiting • IP Filtering │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Application Security │ │
│ │ HTTPS • CORS • CSP • Security Headers • Input Validation │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Authentication & Authorization │ │
│ │ OAuth 2.0 • JWT • MFA • RBAC • Session Management │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Data Security │ │
│ │ Encryption at Rest • TLS • Field Encryption • Anonymization │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Infrastructure Security │ │
│ │ Container Security • Network Segmentation • Secrets Management │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
## Authentication & Authorization
### OAuth 2.0/OIDC Implementation
#### Zitadel Authentication Flow
```go
// Secure authentication implementation
package auth
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/argon2"
)
type SecureAuthService struct {
zitadelRepo ZitadelRepository
keyStore *KeyStore
sessionManager *SessionManager
auditLogger *AuditLogger
rateLimiter *RateLimiter
}
type AuthClaims struct {
jwt.RegisteredClaims
Email string `json:"email"`
Name string `json:"name"`
Roles []string `json:"urn:zitadel:iam:org:project:roles"`
MFAVerified bool `json:"amr"`
SessionID string `json:"sid"`
DeviceID string `json:"device_id,omitempty"`
IPAddress string `json:"ip_address,omitempty"`
}
// Token validation with comprehensive security checks
func (s *SecureAuthService) ValidateToken(ctx context.Context, tokenString string, clientIP string) (*AuthClaims, error) {
// Rate limiting check
if !s.rateLimiter.Allow(fmt.Sprintf("token_validation:%s", clientIP)) {
s.auditLogger.LogSecurityEvent("rate_limit_exceeded", "", clientIP, "warning", map[string]interface{}{
"operation": "token_validation",
})
return nil, fmt.Errorf("rate limit exceeded")
}
// Parse and validate JWT structure
token, err := jwt.ParseWithClaims(tokenString, &AuthClaims{}, func(token *jwt.Token) (interface{}, error) {
// Verify signing algorithm
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// Get key ID from header
keyID, ok := token.Header["kid"].(string)
if !ok {
return nil, fmt.Errorf("missing key ID in token header")
}
// Retrieve public key
return s.keyStore.GetPublicKey(keyID)
})
if err != nil {
s.auditLogger.LogSecurityEvent("invalid_token", "", clientIP, "warning", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("token validation failed: %w", err)
}
if !token.Valid {
return nil, fmt.Errorf("invalid token")
}
claims, ok := token.Claims.(*AuthClaims)
if !ok {
return nil, fmt.Errorf("invalid token claims")
}
// Additional security validations
if err := s.validateTokenClaims(claims, clientIP); err != nil {
s.auditLogger.LogSecurityEvent("token_validation_failed", claims.Subject, clientIP, "warning", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
// Check if session is still valid
if !s.sessionManager.IsValidSession(claims.SessionID, claims.Subject) {
s.auditLogger.LogSecurityEvent("invalid_session", claims.Subject, clientIP, "warning", map[string]interface{}{
"session_id": claims.SessionID,
})
return nil, fmt.Errorf("session no longer valid")
}
// Log successful validation
s.auditLogger.LogSecurityEvent("token_validated", claims.Subject, clientIP, "info", map[string]interface{}{
"session_id": claims.SessionID,
"roles": claims.Roles,
})
return claims, nil
}
func (s *SecureAuthService) validateTokenClaims(claims *AuthClaims, clientIP string) error {
now := time.Now()
// Check expiration
if claims.ExpiresAt != nil && claims.ExpiresAt.Time.Before(now) {
return fmt.Errorf("token expired")
}
// Check not before
if claims.NotBefore != nil && claims.NotBefore.Time.After(now) {
return fmt.Errorf("token not yet valid")
}
// Check issued at (prevent future tokens)
if claims.IssuedAt != nil && claims.IssuedAt.Time.After(now.Add(5*time.Minute)) {
return fmt.Errorf("token issued in the future")
}
// Validate audience
expectedAudience := "knowfoolery-quiz-game"
if !contains(claims.Audience, expectedAudience) {
return fmt.Errorf("invalid audience")
}
// Validate issuer
expectedIssuer := "https://auth.knowfoolery.com"
if claims.Issuer != expectedIssuer {
return fmt.Errorf("invalid issuer")
}
// IP address validation (if configured)
if claims.IPAddress != "" && claims.IPAddress != clientIP {
return fmt.Errorf("IP address mismatch")
}
return nil
}
// Multi-Factor Authentication enforcement
func (s *SecureAuthService) RequireMFA(userID string, roles []string) bool {
// Admin users always require MFA
for _, role := range roles {
if role == "admin" {
return true
}
}
// Check if user has high-value permissions
return s.hasHighValuePermissions(roles)
}
func (s *SecureAuthService) hasHighValuePermissions(roles []string) bool {
highValueRoles := []string{"moderator", "question_manager", "user_manager"}
for _, userRole := range roles {
for _, hvRole := range highValueRoles {
if userRole == hvRole {
return true
}
}
}
return false
}
```
### Session Management
```go
// Secure session management
package session
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
"github.com/go-redis/redis/v8"
)
type SessionManager struct {
redis *redis.Client
entropy int
maxAge time.Duration
secureCookie bool
}
type Session struct {
ID string `json:"id"`
UserID string `json:"user_id"`
CreatedAt time.Time `json:"created_at"`
LastSeen time.Time `json:"last_seen"`
IPAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
DeviceID string `json:"device_id,omitempty"`
Roles []string `json:"roles"`
MFAVerified bool `json:"mfa_verified"`
}
func NewSessionManager(redis *redis.Client, secureCookie bool) *SessionManager {
return &SessionManager{
redis: redis,
entropy: 32, // 256 bits of entropy
maxAge: 24 * time.Hour,
secureCookie: secureCookie,
}
}
func (sm *SessionManager) CreateSession(ctx context.Context, userID, ipAddress, userAgent string, roles []string, mfaVerified bool) (*Session, error) {
// Generate cryptographically secure session ID
sessionID, err := sm.generateSecureSessionID()
if err != nil {
return nil, fmt.Errorf("failed to generate session ID: %w", err)
}
session := &Session{
ID: sessionID,
UserID: userID,
CreatedAt: time.Now(),
LastSeen: time.Now(),
IPAddress: ipAddress,
UserAgent: userAgent,
DeviceID: sm.generateDeviceFingerprint(ipAddress, userAgent),
Roles: roles,
MFAVerified: mfaVerified,
}
// Store session in Redis with expiration
sessionKey := fmt.Sprintf("session:%s", sessionID)
sessionData, err := json.Marshal(session)
if err != nil {
return nil, fmt.Errorf("failed to marshal session: %w", err)
}
err = sm.redis.SetEX(ctx, sessionKey, sessionData, sm.maxAge).Err()
if err != nil {
return nil, fmt.Errorf("failed to store session: %w", err)
}
// Store user session mapping for concurrent session limiting
userSessionKey := fmt.Sprintf("user_sessions:%s", userID)
sm.redis.SAdd(ctx, userSessionKey, sessionID)
sm.redis.Expire(ctx, userSessionKey, sm.maxAge)
return session, nil
}
func (sm *SessionManager) GetSession(ctx context.Context, sessionID string) (*Session, error) {
sessionKey := fmt.Sprintf("session:%s", sessionID)
sessionData, err := sm.redis.Get(ctx, sessionKey).Result()
if err == redis.Nil {
return nil, fmt.Errorf("session not found")
} else if err != nil {
return nil, fmt.Errorf("failed to retrieve session: %w", err)
}
var session Session
err = json.Unmarshal([]byte(sessionData), &session)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal session: %w", err)
}
return &session, nil
}
func (sm *SessionManager) IsValidSession(sessionID, userID string) bool {
ctx := context.Background()
session, err := sm.GetSession(ctx, sessionID)
if err != nil {
return false
}
// Check if session belongs to the correct user
if session.UserID != userID {
return false
}
// Update last seen timestamp
session.LastSeen = time.Now()
sm.updateSession(ctx, session)
return true
}
func (sm *SessionManager) InvalidateSession(ctx context.Context, sessionID string) error {
session, err := sm.GetSession(ctx, sessionID)
if err != nil {
return err
}
// Remove from Redis
sessionKey := fmt.Sprintf("session:%s", sessionID)
sm.redis.Del(ctx, sessionKey)
// Remove from user sessions set
userSessionKey := fmt.Sprintf("user_sessions:%s", session.UserID)
sm.redis.SRem(ctx, userSessionKey, sessionID)
return nil
}
func (sm *SessionManager) InvalidateAllUserSessions(ctx context.Context, userID string) error {
userSessionKey := fmt.Sprintf("user_sessions:%s", userID)
sessionIDs, err := sm.redis.SMembers(ctx, userSessionKey).Result()
if err != nil {
return err
}
// Remove all sessions
for _, sessionID := range sessionIDs {
sessionKey := fmt.Sprintf("session:%s", sessionID)
sm.redis.Del(ctx, sessionKey)
}
// Clear user sessions set
sm.redis.Del(ctx, userSessionKey)
return nil
}
func (sm *SessionManager) generateSecureSessionID() (string, error) {
bytes := make([]byte, sm.entropy)
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
// Hash the random bytes for additional security
hash := sha256.Sum256(bytes)
return hex.EncodeToString(hash[:]), nil
}
func (sm *SessionManager) generateDeviceFingerprint(ipAddress, userAgent string) string {
data := fmt.Sprintf("%s:%s", ipAddress, userAgent)
hash := sha256.Sum256([]byte(data))
return hex.EncodeToString(hash[:16]) // 128-bit fingerprint
}
func (sm *SessionManager) updateSession(ctx context.Context, session *Session) error {
sessionKey := fmt.Sprintf("session:%s", session.ID)
sessionData, err := json.Marshal(session)
if err != nil {
return err
}
return sm.redis.SetEX(ctx, sessionKey, sessionData, sm.maxAge).Err()
}
```
## Input Validation & Sanitization
### Comprehensive Input Validation
```go
// Input validation and sanitization framework
package validation
import (
"fmt"
"html"
"regexp"
"strings"
"unicode"
"github.com/go-playground/validator/v10"
)
type InputValidator struct {
validator *validator.Validate
rules map[string]*ValidationRule
}
type ValidationRule struct {
MaxLength int
MinLength int
Pattern *regexp.Regexp
AllowedChars *regexp.Regexp
Sanitizer func(string) string
}
func NewInputValidator() *InputValidator {
v := validator.New()
// Register custom validations
v.RegisterValidation("alphanum_space", validateAlphanumSpace)
v.RegisterValidation("no_html", validateNoHTML)
v.RegisterValidation("safe_text", validateSafeText)
iv := &InputValidator{
validator: v,
rules: make(map[string]*ValidationRule),
}
iv.setupValidationRules()
return iv
}
func (iv *InputValidator) setupValidationRules() {
// Player name validation
iv.rules["player_name"] = &ValidationRule{
MaxLength: 50,
MinLength: 2,
AllowedChars: regexp.MustCompile(`^[a-zA-Z0-9\s\-_.]+$`),
Sanitizer: iv.sanitizePlayerName,
}
// Answer validation
iv.rules["answer"] = &ValidationRule{
MaxLength: 500,
MinLength: 1,
AllowedChars: regexp.MustCompile(`^[a-zA-Z0-9\s\-_.,'!?()]+$`),
Sanitizer: iv.sanitizeAnswer,
}
// Question text validation (admin only)
iv.rules["question_text"] = &ValidationRule{
MaxLength: 1000,
MinLength: 10,
Sanitizer: iv.sanitizeQuestionText,
}
// Theme validation
iv.rules["theme"] = &ValidationRule{
MaxLength: 100,
MinLength: 2,
AllowedChars: regexp.MustCompile(`^[a-zA-Z0-9\s\-_]+$`),
Sanitizer: iv.sanitizeTheme,
}
}
// Validate and sanitize input based on field type
func (iv *InputValidator) ValidateAndSanitize(fieldType, input string) (string, error) {
rule, exists := iv.rules[fieldType]
if !exists {
return "", fmt.Errorf("unknown field type: %s", fieldType)
}
// Basic length validation
if len(input) < rule.MinLength {
return "", fmt.Errorf("input too short: minimum %d characters", rule.MinLength)
}
if len(input) > rule.MaxLength {
return "", fmt.Errorf("input too long: maximum %d characters", rule.MaxLength)
}
// Character validation
if rule.AllowedChars != nil && !rule.AllowedChars.MatchString(input) {
return "", fmt.Errorf("input contains invalid characters")
}
// Sanitize input
sanitized := input
if rule.Sanitizer != nil {
sanitized = rule.Sanitizer(input)
}
return sanitized, nil
}
// Sanitization functions
func (iv *InputValidator) sanitizePlayerName(input string) string {
// Remove HTML entities and tags
sanitized := html.EscapeString(input)
// Trim whitespace
sanitized = strings.TrimSpace(sanitized)
// Remove multiple consecutive spaces
spaceRegex := regexp.MustCompile(`\s+`)
sanitized = spaceRegex.ReplaceAllString(sanitized, " ")
return sanitized
}
func (iv *InputValidator) sanitizeAnswer(input string) string {
// HTML escape
sanitized := html.EscapeString(input)
// Trim and normalize whitespace
sanitized = strings.TrimSpace(sanitized)
// Convert to lowercase for comparison
sanitized = strings.ToLower(sanitized)
// Remove extra punctuation but keep essential ones
punctRegex := regexp.MustCompile(`[^\w\s\-'.]`)
sanitized = punctRegex.ReplaceAllString(sanitized, "")
return sanitized
}
func (iv *InputValidator) sanitizeQuestionText(input string) string {
// More permissive sanitization for question text
sanitized := html.EscapeString(input)
sanitized = strings.TrimSpace(sanitized)
// Remove potential script content
scriptRegex := regexp.MustCompile(`(?i)`)
sanitized = scriptRegex.ReplaceAllString(sanitized, "")
return sanitized
}
func (iv *InputValidator) sanitizeTheme(input string) string {
sanitized := html.EscapeString(input)
sanitized = strings.TrimSpace(sanitized)
// Capitalize first letter of each word
words := strings.Fields(sanitized)
for i, word := range words {
if len(word) > 0 {
words[i] = strings.ToUpper(string(word[0])) + strings.ToLower(word[1:])
}
}
return strings.Join(words, " ")
}
// Custom validation functions
func validateAlphanumSpace(fl validator.FieldLevel) bool {
str := fl.Field().String()
for _, r := range str {
if !unicode.IsLetter(r) && !unicode.IsNumber(r) && !unicode.IsSpace(r) && r != '-' && r != '_' && r != '.' {
return false
}
}
return true
}
func validateNoHTML(fl validator.FieldLevel) bool {
str := fl.Field().String()
return !strings.Contains(str, "<") && !strings.Contains(str, ">")
}
func validateSafeText(fl validator.FieldLevel) bool {
str := fl.Field().String()
// Check for potential XSS patterns
dangerousPatterns := []string{
"javascript:",
"data:",
"vbscript:",
"on\\w+\\s*=",
"