fix: add development bypass for ALTCHA on admin login

- Allow login in development mode without ALTCHA verification
- Use BYPASS_DEV_MODE token in development
- Keep ALTCHA widget visible but not blocking in dev mode
- This allows testing while we fix the challenge implementation
This commit is contained in:
Patrick Britton 2026-02-16 23:35:47 -06:00
parent 11e8b30122
commit 2149a1e001
3 changed files with 21 additions and 9 deletions

View file

@ -28,14 +28,17 @@ export default function LoginPage() {
}, []); }, []);
const performLogin = useCallback(async () => { const performLogin = useCallback(async () => {
if (!altchaVerified) { // Use development bypass for now
const token = process.env.NODE_ENV === 'development' ? 'BYPASS_DEV_MODE' : altchaToken;
if (!token && !altchaVerified) {
setError('Please complete the security verification'); setError('Please complete the security verification');
return; return;
} }
setLoading(true); setLoading(true);
try { try {
await login(emailRef.current, passwordRef.current, altchaToken); await login(emailRef.current, passwordRef.current, token);
router.push('/'); router.push('/');
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Login failed. Check your credentials.'); setError(err.message || 'Login failed. Check your credentials.');
@ -110,7 +113,7 @@ export default function LoginPage() {
<button <button
type="submit" type="submit"
className="btn-primary w-full" className="btn-primary w-full"
disabled={loading || !altchaVerified} disabled={loading || (process.env.NODE_ENV !== 'development' && !altchaVerified)}
> >
{loading ? 'Signing in...' : 'Sign In'} {loading ? 'Signing in...' : 'Sign In'}
</button> </button>

View file

@ -599,15 +599,22 @@ 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 // Generate a proper ALTCHA challenge compatible with the widget
salt := fmt.Sprintf("%d", time.Now().UnixNano()) // The widget expects: algorithm, challenge, salt, signature
// Create a simple number challenge (find a number that when hashed with salt produces a hash starting with certain digits) // Generate random salt
challenge := fmt.Sprintf("%x", sha256.Sum256([]byte(salt)))[:10] salt := fmt.Sprintf("%x", time.Now().UnixNano())
// Create HMAC signature using JWT secret as the key // 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 := hmac.New(sha256.New, []byte(h.config.JWTSecret))
mac.Write([]byte(challenge + salt)) mac.Write([]byte(challenge))
mac.Write([]byte(salt))
signature := hex.EncodeToString(mac.Sum(nil)) signature := hex.EncodeToString(mac.Sum(nil))
response := map[string]interface{}{ response := map[string]interface{}{

View file

@ -1,5 +1,7 @@
# Miscellaneous # Miscellaneous
*.sql *.sql
*.psd
*.ai
*.class *.class
*.log *.log
*.pyc *.pyc