feat: implement official ALTCHA library for proper challenge generation

- Install github.com/altcha-org/altcha-lib-go official library
- Replace custom implementation with official ALTCHA methods
- Use CreateChallenge for proper cryptographic challenges
- Use VerifySolution for proper token verification
- This should fix all ALTCHA verification issues
This commit is contained in:
Patrick Britton 2026-02-16 23:40:43 -06:00
parent 2149a1e001
commit db0e3dfb59
4 changed files with 87 additions and 119 deletions

View file

@ -3,9 +3,6 @@ package handlers
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -4133,23 +4130,13 @@ func (h *AdminHandler) SendTestEmail(c *gin.Context) {
} }
func (h *AdminHandler) GetAltchaChallenge(c *gin.Context) { func (h *AdminHandler) GetAltchaChallenge(c *gin.Context) {
// Generate a proper ALTCHA challenge altchaService := services.NewAltchaService(h.jwtSecret)
salt := fmt.Sprintf("%d", time.Now().UnixNano())
// Create a simple number challenge (find a number that when hashed with salt produces a hash starting with certain digits) challenge, err := altchaService.GenerateChallenge()
challenge := fmt.Sprintf("%x", sha256.Sum256([]byte(salt)))[:10] if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate challenge"})
// Create HMAC signature using JWT secret as the key return
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,
} }
c.JSON(http.StatusOK, response) c.JSON(http.StatusOK, challenge)
} }

View file

@ -1,12 +1,10 @@
package handlers package handlers
import ( import (
"crypto/hmac"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"fmt"
"net/http" "net/http"
"time" "time"
@ -599,30 +597,13 @@ func (h *AuthHandler) ResetPassword(c *gin.Context) {
} }
func (h *AuthHandler) GetAltchaChallenge(c *gin.Context) { func (h *AuthHandler) GetAltchaChallenge(c *gin.Context) {
// Generate a proper ALTCHA challenge compatible with the widget altchaService := services.NewAltchaService(h.config.JWTSecret)
// The widget expects: algorithm, challenge, salt, signature
// Generate random salt challenge, err := altchaService.GenerateChallenge()
salt := fmt.Sprintf("%x", time.Now().UnixNano()) if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate challenge"})
// Generate a random number that needs to be found (the challenge) return
// 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,
} }
c.JSON(http.StatusOK, response) c.JSON(http.StatusOK, challenge)
} }

View file

@ -1,18 +1,13 @@
package services package services
import ( import (
"crypto/sha256"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"net/http" altcha "github.com/altcha-org/altcha-lib-go"
"strings"
"time"
) )
type AltchaService struct { type AltchaService struct {
secretKey string secretKey string
client *http.Client
} }
type AltchaResponse struct { type AltchaResponse struct {
@ -20,25 +15,15 @@ type AltchaResponse struct {
Error string `json:"error,omitempty"` 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 { func NewAltchaService(secretKey string) *AltchaService {
return &AltchaService{ return &AltchaService{
secretKey: secretKey, 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) { 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" { if token == "BYPASS_DEV_MODE" {
return &AltchaResponse{Verified: true}, nil return &AltchaResponse{Verified: true}, nil
} }
@ -48,50 +33,40 @@ func (s *AltchaService) VerifyToken(token, remoteIP string) (*AltchaResponse, er
return &AltchaResponse{Verified: true}, nil return &AltchaResponse{Verified: true}, nil
} }
// Parse the ALTCHA response // Verify using official ALTCHA library
var altchaData AltchaChallenge ok, err := altcha.VerifySolution(token, s.secretKey, true)
if err := json.Unmarshal([]byte(token), &altchaData); err != nil { if err != nil {
return &AltchaResponse{Verified: false, Error: "Invalid token format"}, nil return &AltchaResponse{Verified: false, Error: err.Error()}, nil
} }
// Verify the signature if !ok {
expectedSignature := s.generateSignature(altchaData.Algorithm, altchaData.Challenge, altchaData.Salt) return &AltchaResponse{Verified: false, Error: "Invalid solution"}, nil
if !strings.EqualFold(altchaData.Signature, expectedSignature) {
return &AltchaResponse{Verified: false, Error: "Invalid signature"}, 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 return &AltchaResponse{Verified: true}, nil
} }
// GenerateChallenge creates a new ALTCHA challenge // GenerateChallenge creates a new ALTCHA challenge using the official library
func (s *AltchaService) GenerateChallenge() (*AltchaChallenge, error) { func (s *AltchaService) GenerateChallenge() (map[string]interface{}, error) {
if s.secretKey == "" { // Generate challenge using official ALTCHA library
return nil, fmt.Errorf("ALTCHA secret key not configured") // 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) // Convert to map for JSON response
challenge := fmt.Sprintf("%d", time.Now().UnixNano()) var result map[string]interface{}
salt := fmt.Sprintf("%d", time.Now().Unix()) data, err := json.Marshal(challenge)
algorithm := "SHA-256" 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{ return result, nil
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[:])
} }
// GetErrorMessage returns a user-friendly error message // GetErrorMessage returns a user-friendly error message
@ -100,6 +75,7 @@ func (s *AltchaService) GetErrorMessage(error string) string {
"Invalid token format": "Invalid security verification format", "Invalid token format": "Invalid security verification format",
"Invalid signature": "Security verification failed", "Invalid signature": "Security verification failed",
"Challenge expired": "Security verification expired", "Challenge expired": "Security verification expired",
"Invalid solution": "Security verification failed",
"ALTCHA secret key not configured": "Server configuration error", "ALTCHA secret key not configured": "Server configuration error",
} }

64
sojorn_app/.gitignore vendored
View file

@ -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 *.sql
*.psd *.psd
*.ai *.ai
@ -15,34 +34,39 @@
.swiftpm/ .swiftpm/
migrate_working_dir/ migrate_working_dir/
# IntelliJ related # --- IDE SPECIFIC (IntelliJ & VS Code) ---
*.iml *.iml
*.ipr *.ipr
*.iws *.iws
.idea/ .idea/
.vscode/
# The .vscode folder contains launch configuration and tasks you configure in # --- GO BACKEND (go-backend/) ---
# VS Code which you may wish to be included in version control, so this line go-backend/bin/
# is commented out by default. go-backend/out/
#.vscode/ 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/ **/doc/api/
**/ios/Flutter/.last_build_id **/ios/Flutter/.last_build_id
.dart_tool/ sojorn_app/.dart_tool/
.flutter-plugins-dependencies sojorn_app/.flutter-plugins
.pub-cache/ sojorn_app/.flutter-plugins-dependencies
.pub/ sojorn_app/.packages
/build/ sojorn_app/.pub-cache/
/coverage/ sojorn_app/.pub/
sojorn_app/build/
sojorn_app/coverage/
# Symbolication related # Android Studio / Android Specific
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
# Symbolication & Obfuscation
app.*.symbols
app.*.map.json