diff --git a/go-backend/internal/handlers/admin_handler.go b/go-backend/internal/handlers/admin_handler.go index 8aaa116..6a20076 100644 --- a/go-backend/internal/handlers/admin_handler.go +++ b/go-backend/internal/handlers/admin_handler.go @@ -3,9 +3,6 @@ package handlers import ( "bytes" "context" - "crypto/hmac" - "crypto/sha256" - "encoding/hex" "encoding/json" "fmt" "io" @@ -4133,23 +4130,13 @@ func (h *AdminHandler) SendTestEmail(c *gin.Context) { } func (h *AdminHandler) GetAltchaChallenge(c *gin.Context) { - // Generate a proper ALTCHA challenge - salt := fmt.Sprintf("%d", time.Now().UnixNano()) + altchaService := services.NewAltchaService(h.jwtSecret) - // Create a simple number challenge (find a number that when hashed with salt produces a hash starting with certain digits) - challenge := fmt.Sprintf("%x", sha256.Sum256([]byte(salt)))[:10] - - // Create HMAC signature using JWT secret as the key - mac := hmac.New(sha256.New, []byte(h.jwtSecret)) - mac.Write([]byte(challenge + salt)) - signature := hex.EncodeToString(mac.Sum(nil)) - - response := map[string]interface{}{ - "algorithm": "SHA-256", - "challenge": challenge, - "salt": salt, - "signature": signature, + challenge, err := altchaService.GenerateChallenge() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate challenge"}) + return } - c.JSON(http.StatusOK, response) + c.JSON(http.StatusOK, challenge) } diff --git a/go-backend/internal/handlers/auth_handler.go b/go-backend/internal/handlers/auth_handler.go index 01546e9..70a0453 100644 --- a/go-backend/internal/handlers/auth_handler.go +++ b/go-backend/internal/handlers/auth_handler.go @@ -1,12 +1,10 @@ package handlers import ( - "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" - "fmt" "net/http" "time" @@ -599,30 +597,13 @@ func (h *AuthHandler) ResetPassword(c *gin.Context) { } func (h *AuthHandler) GetAltchaChallenge(c *gin.Context) { - // Generate a proper ALTCHA challenge compatible with the widget - // The widget expects: algorithm, challenge, salt, signature + altchaService := services.NewAltchaService(h.config.JWTSecret) - // Generate random salt - salt := fmt.Sprintf("%x", time.Now().UnixNano()) - - // Generate a random number that needs to be found (the challenge) - // The widget will try to find a number that when combined with salt produces a hash with specific properties - randomBytes := make([]byte, 16) - rand.Read(randomBytes) - challenge := fmt.Sprintf("%x", randomBytes) - - // Create HMAC signature: HMAC(secret, challenge + salt) - mac := hmac.New(sha256.New, []byte(h.config.JWTSecret)) - mac.Write([]byte(challenge)) - mac.Write([]byte(salt)) - signature := hex.EncodeToString(mac.Sum(nil)) - - response := map[string]interface{}{ - "algorithm": "SHA-256", - "challenge": challenge, - "salt": salt, - "signature": signature, + challenge, err := altchaService.GenerateChallenge() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate challenge"}) + return } - c.JSON(http.StatusOK, response) + c.JSON(http.StatusOK, challenge) } diff --git a/go-backend/internal/services/altcha_service.go b/go-backend/internal/services/altcha_service.go index 55db9bd..1ffeb94 100644 --- a/go-backend/internal/services/altcha_service.go +++ b/go-backend/internal/services/altcha_service.go @@ -1,18 +1,13 @@ package services import ( - "crypto/sha256" - "encoding/hex" "encoding/json" - "fmt" - "net/http" - "strings" - "time" + + altcha "github.com/altcha-org/altcha-lib-go" ) type AltchaService struct { secretKey string - client *http.Client } type AltchaResponse struct { @@ -20,25 +15,15 @@ type AltchaResponse struct { Error string `json:"error,omitempty"` } -type AltchaChallenge struct { - Algorithm string `json:"algorithm"` - Challenge string `json:"challenge"` - Salt string `json:"salt"` - Signature string `json:"signature"` -} - func NewAltchaService(secretKey string) *AltchaService { return &AltchaService{ secretKey: secretKey, - client: &http.Client{ - Timeout: 10 * time.Second, - }, } } -// VerifyToken validates an ALTCHA token +// VerifyToken validates an ALTCHA token using the official library func (s *AltchaService) VerifyToken(token, remoteIP string) (*AltchaResponse, error) { - // Allow bypass token for development (Flutter web) + // Allow bypass token for development if token == "BYPASS_DEV_MODE" { return &AltchaResponse{Verified: true}, nil } @@ -48,58 +33,49 @@ func (s *AltchaService) VerifyToken(token, remoteIP string) (*AltchaResponse, er return &AltchaResponse{Verified: true}, nil } - // Parse the ALTCHA response - var altchaData AltchaChallenge - if err := json.Unmarshal([]byte(token), &altchaData); err != nil { - return &AltchaResponse{Verified: false, Error: "Invalid token format"}, nil + // Verify using official ALTCHA library + ok, err := altcha.VerifySolution(token, s.secretKey, true) + if err != nil { + return &AltchaResponse{Verified: false, Error: err.Error()}, nil } - // Verify the signature - expectedSignature := s.generateSignature(altchaData.Algorithm, altchaData.Challenge, altchaData.Salt) - if !strings.EqualFold(altchaData.Signature, expectedSignature) { - return &AltchaResponse{Verified: false, Error: "Invalid signature"}, nil + if !ok { + return &AltchaResponse{Verified: false, Error: "Invalid solution"}, nil } - // Verify the challenge solution (simple hash verification for now) - // In a real implementation, you'd solve the actual puzzle - // For now, we'll accept any valid signature as verified return &AltchaResponse{Verified: true}, nil } -// GenerateChallenge creates a new ALTCHA challenge -func (s *AltchaService) GenerateChallenge() (*AltchaChallenge, error) { - if s.secretKey == "" { - return nil, fmt.Errorf("ALTCHA secret key not configured") +// GenerateChallenge creates a new ALTCHA challenge using the official library +func (s *AltchaService) GenerateChallenge() (map[string]interface{}, error) { + // Generate challenge using official ALTCHA library + // Parameters: hmacKey, maxNumber (difficulty), saltLength, algorithm, expiresInSeconds + challenge, err := altcha.CreateChallenge(s.secretKey, 100000, 12, "SHA-256", 300) + if err != nil { + return nil, err } - // Generate a simple challenge (in production, use proper puzzle generation) - challenge := fmt.Sprintf("%d", time.Now().UnixNano()) - salt := fmt.Sprintf("%d", time.Now().Unix()) - algorithm := "SHA-256" + // Convert to map for JSON response + var result map[string]interface{} + data, err := json.Marshal(challenge) + if err != nil { + return nil, err + } - signature := s.generateSignature(algorithm, challenge, salt) + if err := json.Unmarshal(data, &result); err != nil { + return nil, err + } - return &AltchaChallenge{ - Algorithm: algorithm, - Challenge: challenge, - Salt: salt, - Signature: signature, - }, nil -} - -// generateSignature creates HMAC signature for ALTCHA -func (s *AltchaService) generateSignature(algorithm, challenge, salt string) string { - data := algorithm + challenge + salt - hash := sha256.Sum256([]byte(data + s.secretKey)) - return hex.EncodeToString(hash[:]) + return result, nil } // GetErrorMessage returns a user-friendly error message func (s *AltchaService) GetErrorMessage(error string) string { errorMessages := map[string]string{ - "Invalid token format": "Invalid security verification format", - "Invalid signature": "Security verification failed", - "Challenge expired": "Security verification expired", + "Invalid token format": "Invalid security verification format", + "Invalid signature": "Security verification failed", + "Challenge expired": "Security verification expired", + "Invalid solution": "Security verification failed", "ALTCHA secret key not configured": "Server configuration error", } diff --git a/sojorn_app/.gitignore b/sojorn_app/.gitignore index f1c063a..f127e43 100644 --- a/sojorn_app/.gitignore +++ b/sojorn_app/.gitignore @@ -1,4 +1,23 @@ -# Miscellaneous +# ########################################################### +# MP.LS LLC - Unified Sojorn Monorepo .gitignore +# Combined with User Workflow Preferences +# ########################################################### + +# --- CRITICAL: SENSITIVE DATA (NEVER COMMIT) --- +.env +*.env +auth.mp.ls.key +google-services.json +GoogleService-Info.plist +firebase_options.dart +*.keystore +*.jks +key.properties +# Tax/Business docs (Safety precaution) +*_tax_*.pdf +W-2_*.pdf + +# --- MISCELLANEOUS & MEDIA --- *.sql *.psd *.ai @@ -15,34 +34,39 @@ .swiftpm/ migrate_working_dir/ -# IntelliJ related +# --- IDE SPECIFIC (IntelliJ & VS Code) --- *.iml *.ipr *.iws .idea/ +.vscode/ -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ +# --- GO BACKEND (go-backend/) --- +go-backend/bin/ +go-backend/out/ +go-backend/*.exe +go-backend/*.test +go-backend/*.prof +go-backend/vendor/ +go-backend/config.yaml -# Flutter/Dart/Pub related +# --- FLUTTER APP (sojorn_app/) --- **/doc/api/ **/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins-dependencies -.pub-cache/ -.pub/ -/build/ -/coverage/ +sojorn_app/.dart_tool/ +sojorn_app/.flutter-plugins +sojorn_app/.flutter-plugins-dependencies +sojorn_app/.packages +sojorn_app/.pub-cache/ +sojorn_app/.pub/ +sojorn_app/build/ +sojorn_app/coverage/ -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here +# Android Studio / Android Specific /android/app/debug /android/app/profile /android/app/release + +# Symbolication & Obfuscation +app.*.symbols +app.*.map.json \ No newline at end of file