# m2fa documentation

Private OTP vault: **https://m2fa.strogleapp.com**

Give a model [llm.txt](https://m2fa.strogleapp.com/llm.txt) so it does not invent endpoints.

| File | Open | Download | What it is |
| --- | --- | --- | --- |
| LLM spec | https://m2fa.strogleapp.com/llm.txt | https://m2fa.strogleapp.com/llm.txt?download=1 | Machine brief. Alias `/llms.txt`. |
| This markdown | https://m2fa.strogleapp.com/docs.md | https://m2fa.strogleapp.com/docs.md?download=1 | Human REST notes. |
| HTML | https://m2fa.strogleapp.com/docs | https://m2fa.strogleapp.com/docs?download=1 | Readable page with copy / download / link. |

## Overview

m2fa stores TOTP, HOTP, and Steam one-time-password secrets for people you create. Each user (including the admin vault) has an API key. The Chrome extension and any script use that key on HTTPS. The dashboard itself does not talk REST from the browser; it uses a WebSocket (`wss://m2fa.strogleapp.com/ws/`).

Secrets are encrypted at rest. Listing accounts never returns secrets. Codes are computed on demand.

## Get a key

1. Sign in at https://m2fa.strogleapp.com (username/password), or open **Login dashboard manager** with the admin PIN.
2. Open **API** (or **Users** as admin) and copy the `m2fa_…` key.
3. Paste it into the extension Options, or send it as `X-Api-Key`.

Do not put live keys in shared docs. Use `$M2FA_KEY`.

## REST API

Base: `https://m2fa.strogleapp.com`

| Method | Path | Auth | Notes |
| --- | --- | --- | --- |
| GET | `/health` | no | `{ "ok": true, "service": "m2fa" }` |
| GET | `/auths` | key | Account list for this key. No secrets. |
| GET | `/auths/{id}` | key | Current OTP. HOTP increments counter. |
| POST | `/add` | key | Create account. **201** on success. |
| GET | `/logos/{slug}` | no | Cached brand logo. |
| GET | `/auths/icons/{name}` | key | Logo or letter badge. |
| GET | `/llm.txt` | no | Machine spec. Alias `/llms.txt`. `?download=1` to save. |
| GET | `/docs.md` | no | This file. `?download=1` to save. |
| GET | `/docs` | no | HTML docs. Also `/docs.html`. `?download=1` to save. |

Headers (pick one):

```
X-Api-Key: m2fa_…
X-Flow-Hash: m2fa_…
Authorization: Bearer m2fa_…
```

Rate limit: 120 calls / 60 seconds / IP.

### List

```bash
curl -sS https://m2fa.strogleapp.com/auths \
  -H "X-Api-Key: $M2FA_KEY"
```

Each item has `id`, `service`, `account`, `icon`, `logo`, `logo_url`, `otp_type`, `digits`, `period`, `algorithm`. No secret.

### Current code

```bash
curl -sS https://m2fa.strogleapp.com/auths/12 \
  -H "X-Api-Key: $M2FA_KEY"
```

Response includes `password`, `next_password`, `period`, `generated_at`, `otp_type`. HOTP also returns the next `counter`.

### Add (otpauth URI)

```bash
curl -sS -X POST https://m2fa.strogleapp.com/add \
  -H "X-Api-Key: $M2FA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"uri":"otpauth://totp/GitHub:you?secret=BASE32&issuer=GitHub"}'
```

Must return **201**. Adding also tries to cache an official logo for the service name.

Manual body (same route): `service`, `account`, `secret`, optional `otp_type`, `digits`, `algorithm`, `period`, `counter`.

## Dashboard WSS

`wss://m2fa.strogleapp.com/ws/` after Cloudflare. Wait for `bridge.ready`, then send JSON `{ "command": "auth.login", "username", "password" }` or `{ "command": "auth.admin", "pin" }`. Later commands include `session_token`. Users cannot create users; only the manager PIN session can.

Do not open WSS via a bare IP on port 443.

## Errors

| Status | Meaning |
| --- | --- |
| 201 | Created (`POST /add`) |
| 400 | Bad OTP payload / duplicate |
| 403 | Missing or invalid API key |
| 404 | Account id not in this vault |
| 429 | Too many API calls |

## Chrome extension

Load the `extension` folder unpacked, or `ext.zip`. Store the key in Options. Popup lists this vault, copies codes on click, and restores an add-account draft if Chrome closed the popup. Point it at `https://m2fa.strogleapp.com` and send the key as `X-Api-Key` (or `X-Flow-Hash`).

## Moving to another host

- Point the Cloudflare DNS record for `m2fa.strogleapp.com` at the destination and expose the existing HTTPS/WSS stack there.
- Migrate the current MySQL database and original `.env`, especially `SECRET_KEY`, so existing encrypted OTP secrets and API keys remain usable. Update database connection settings for the destination. `PUBLIC_URL` is `https://m2fa.strogleapp.com`.
- The supplied systemd units and installer currently expect `/var/apps/qrmaster`. Use that path on the destination or update their working directory, executable paths, and environment file paths before installation. Recreate `.venv` and install Python and Node dependencies on the destination.
- Use `nginx/m2fa.strogleapp.com` as the project-owned Nginx configuration and symlink it into the destination's enabled sites. Provide the certificate files referenced by that configuration, run `nginx -t`, and reload Nginx. Update any existing site symlink that still targets the former configuration filename.
- Install the services with `./service-installer`. Verify `/health`, authenticated `/auths`, and dashboard WSS at `wss://m2fa.strogleapp.com/ws/`. Only use an OTP generation request when intended: HOTP requests advance the counter.
- Reload extension version `0.3.7` in the existing Chrome extension entry to retain its saved API key. See `extension/README.md` for update instructions.
