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:
parent
2149a1e001
commit
db0e3dfb59
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}
|
||||
|
||||
|
|
|
|||
64
sojorn_app/.gitignore
vendored
64
sojorn_app/.gitignore
vendored
|
|
@ -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
|
||||
Loading…
Reference in a new issue