Skip to content

cloudmosa/authenticator

Repository files navigation

MFA Sync Service

Secure, ephemeral MFA secret key sharing service for Cloud Phone devices, with or without a camera.

Authenticator Design

Availability & Use

  1. Sign up for the Developer Program
  2. Enable Developer Mode on your Cloud Phone device
  3. Add this widget with the URL: https://mfa.cloudphone.tech/sync.html and icon
  4. Visit https://mfa.cloudphone.tech/ on your desktop or mobile (Android or iOS) and scan or enter an MFA secret
  5. Open MFA Sync on Cloud Phone and enter the PIN

Key Uri Format

This app processes data in the Key URI format.

otpauth://totp/Zoho:email@zoho.com?secret=JBSWY3DPEHPK3PXP&period=30&digits=6&algorithm=SHA1&issuer=Zoho

See Key Uri Format.

Architecture

┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│   Phone     │         │              │         │   Feature   │
│   (Admin)   │────────>│  VPS Server  │<────────│    Phone    │
│ Scan QR Code│         │   + Redis    │         │  Enter PIN  │
└─────────────┘         └──────────────┘         └─────────────┘
      │                        │                         │
      │                        │                         │
      └─── Generate PIN ───────┤                         │
                               │                         │
                               └──── Retrieve Secret ────┘

Features

  • Ephemeral Storage: Secrets auto-delete after retrieval or expiration
  • Encrypted at Rest: AES-256-GCM encryption in Redis
  • Rate Limited: Protection against brute force attacks
  • T9-Friendly: Simple numeric PIN entry for feature phones
  • QR Scanner: Built-in camera scanning for admin interface
  • TOTP Generator: Full authenticator app functionality in browser
  • Zero Dependencies: Client-side uses only Web Crypto API

Quick Start

Local Development

# Install dependencies
npm install

# Start Redis
docker run -d -p 6379:6379 redis:7-alpine

# Start server
npm start

# Access at http://localhost:3000

Docker Deployment

# Build and run with Docker Compose
docker-compose up -d

# View logs
docker-compose logs -f

# Stop
docker-compose down

VPS Deployment

Prerequisites

  1. VPS with Docker installed
  2. Domain name (optional, for HTTPS)
  3. GitHub repository secrets configured

GitHub Secrets Setup

Add these secrets to your GitHub repository:

VPS_HOST=your.server.ip
VPS_USER=your-ssh-user
VPS_SSH_KEY=<your-private-ssh-key>
ALLOWED_ORIGINS=https://mfa.cloudphone.tech

Server Setup

# SSH into your VPS
ssh user@your-server

# Install Docker (if not installed)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

# Create deployment directory
sudo mkdir -p /opt/mfa-sync
sudo chown $USER:$USER /opt/mfa-sync
cd /opt/mfa-sync

# Clone repository
git clone https://github.com/yourusername/mfa-sync.git .

# Create docker-compose.yml
cp docker-compose.yml /opt/mfa-sync/

# Start services
docker-compose up -d

SSL Certificate (Let's Encrypt)

# Install certbot
sudo apt-get update
sudo apt-get install certbot

# Generate certificate
sudo certbot certonly --standalone -d mfa.cloudphone.tech

# Copy certificates to nginx directory
sudo mkdir -p /opt/mfa-sync/ssl
sudo cp /etc/letsencrypt/live/mfa.cloudphone.tech/fullchain.pem /opt/mfa-sync/ssl/cert.pem
sudo cp /etc/letsencrypt/live/mfa.cloudphone.tech/privkey.pem /opt/mfa-sync/ssl/key.pem
sudo chown -R $USER:$USER /opt/mfa-sync/ssl

# Restart nginx
docker-compose restart nginx

Usage

1. Admin Interface

Access from your smartphone with a camera:

https://mfa.cloudphone.tech.com/
  1. Click "📷 Scan QR Code"
  2. Point camera at MFA QR code
  3. Secret auto-fills, or paste manually
  4. Click "Generate PIN"
  5. Display shows 6-digit PIN

2. Feature Phone Interface (sync.html)

Access from your feature phone browser:

https://mfa.cloudphone.tech/sync.html
  1. Enter the 6-digit PIN from admin interface
  2. Click "Retrieve Secret" (or press Enter)
  3. Account added with live TOTP codes
  4. Codes refresh every 30 seconds

API Reference

POST /api/store

Store an MFA secret and generate PIN.

Request:

{
  "secret": "JBSWY3DPEHPK3PXP",
  "label": "GitHub",
  "issuer": "GitHub",
  "ttl": 300
}

Response:

{
  "pin": "123456",
  "expiresIn": 300,
  "expiresAt": "2025-10-22T10:15:00.000Z"
}

POST /api/retrieve

Retrieve MFA secret with PIN (one-time use).

Request:

{
  "pin": "123456"
}

Response:

{
  "secret": "JBSWY3DPEHPK3PXP",
  "label": "GitHub",
  "issuer": "GitHub",
  "type": "totp",
  "algorithm": "SHA1",
  "digits": 6,
  "period": 30
}

GET /api/health

Health check endpoint.

Response:

{
  "status": "healthy",
  "redis": "connected"
}

Security Considerations

What This Service Does

✅ Provides secure, temporary exchange of MFA secrets ✅ Encrypts secrets at rest with AES-256-GCM ✅ Automatically deletes secrets after retrieval ✅ Rate limits to prevent brute force attacks ✅ Uses in-memory storage (Redis) with TTL ✅ One-time PIN retrieval only

What You Should Do

⚠️ Use HTTPS only - Never deploy without SSL/TLS ⚠️ Restrict ALLOWED_ORIGINS - Limit to your domain ⚠️ Use strong firewall rules - Limit Redis access ⚠️ Monitor logs - Watch for suspicious activity ⚠️ Keep secrets short-lived - Use 5-10 minute TTLs ⚠️ Use on trusted networks - Avoid public WiFi for admin interface

Threat Model

  • ✅ Protects against: Interception, storage leaks, unauthorized access
  • ⚠️ Limited protection: Network-level attacks require HTTPS
  • ❌ Does not protect: Compromised admin device, malicious VPS

Environment Variables

PORT=3000                      # Server port
REDIS_URL=redis://localhost:6379  # Redis connection string
ALLOWED_ORIGINS=*              # CORS origins (comma-separated)
NODE_ENV=production            # Environment
PIN_TTL=120                    # Pin Time to Live (TTL) in seconds

Troubleshooting

Redis Connection Failed

# Check Redis is running
docker ps | grep redis

# View Redis logs
docker-compose logs redis

# Test Redis connection
docker exec -it mfa-sync-redis-1 redis-cli ping

PIN Not Found

  • Check TTL hasn't expired
  • Verify PIN is exactly 6-8 digits
  • Ensure no spaces in PIN entry
  • Check if already retrieved (/api/check/:pin)

Camera Not Working

  • Ensure HTTPS is enabled (required for camera API)
  • Check browser permissions
  • Try different browser (Chrome/Safari recommended)

License

Apache 2.0

Contributing

Issues and pull requests welcome!