Files
Discord-Authenticator/deploy/unraid/discord-authenticator.xml
T
Miles WardandClaude Opus 4.8 eecdacd890 v0.1.1: import Google Authenticator exports
Google's "Transfer accounts -> Export accounts" produces an
otpauth-migration:// link wrapping a base64 protobuf that holds many accounts
at once, and splits large exports across several QR codes. v0.1 rejected these
outright; now they import.

The protobuf is decoded by hand rather than pulling prost: the schema is four
messages that have not changed since 2020, and the wire format needed is two
types (varint, length-delimited). Unknown fields are skipped properly, so a
future Google addition degrades rather than corrupting the parse.

Two details in this format silently produce plausible-but-wrong codes if taken
backwards, so both are pinned by tests: the `secret` field is RAW BYTES, not
base32, and the payload carries NO period field -- every Google export is
30-second TOTP.

- core/migration.rs: protobuf decode, lenient base64 (all four alphabet and
  padding spellings), issuer reconciliation between the `name` prefix and the
  dedicated `issuer` field, and batch metadata surfaced as "Part 1 of 2"
- vault: add_many() with duplicate detection, persisting once per batch rather
  than once per account; duplicates match on secret AND naming, so keeping one
  secret under two labels stays possible
- http-api: preview returns a list flagged with what is already stored; import
  takes the link plus selected indices and re-parses server-side, so secrets
  never travel out to the browser and back. /api/qr/decode now returns every
  payload in an image, not just the first
- webui: multi-account import checklist with select all/none, per-entry
  algorithm and digit chips, already-present entries pre-unticked, and skipped
  entries named with their reason (HOTP, MD5) instead of silently dropped

Verified end to end against real QR images: upload -> decode -> preview ->
import, with the resulting codes cross-checked against an independent RFC 6238
implementation, including an 8-digit account.

124 tests (was 101).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-31 00:44:59 -04:00

77 lines
4.0 KiB
XML

<?xml version="1.0"?>
<!--
Unraid Docker template for Discord Authenticator.
Drop this into /boot/config/plugins/dockerMan/templates-user/ on the Unraid
box, or add the repo as a template repository under Docker → Add Container.
Unlike OpenPXE this needs no host networking — it only makes outbound HTTPS
calls to Discord and serves one HTTP port, so bridge mode is correct.
Web UI: http://<unraid-ip>:8710/
-->
<Container version="2">
<Name>Discord-Authenticator</Name>
<Repository>gitea.milesward.dev/mward4/discord-authenticator:latest</Repository>
<Registry>https://gitea.milesward.dev/mward4/-/packages/container/discord-authenticator</Registry>
<Network>bridge</Network>
<MyIP/>
<Shell>sh</Shell>
<Privileged>false</Privileged>
<Support>https://gitea.milesward.dev/mward4/Discord-Authenticator/issues</Support>
<Project>https://gitea.milesward.dev/mward4/Discord-Authenticator</Project>
<Overview>
Self-hosted TOTP authenticator with live Discord delivery.
Add accounts by uploading a QR code, scanning with the camera, or pasting
an otpauth:// link. Google Authenticator exports are supported too: one
export QR carries many accounts, and you get a checklist to pick which to
import, with duplicates detected automatically. The web UI lists every
account with its rotating code, the way Google or Microsoft Authenticator
does.
The distinctive part: each account keeps ONE message in your Discord
channel, delivered through an incoming webhook and rewritten in place
every time the code rolls. Three accounts means three live tiles that
keep themselves current. The code is posted both as a rendered WebP tile
and as copyable text, and the countdown uses Discord's own relative
timestamps so it ticks in the client without any extra traffic.
SECURITY NOTE: anyone who can read that Discord channel can read these
codes, which is most of the point of a second factor. Use a private
channel in a server only you are in, and treat the webhook URL as a
secret. TOTP secrets are encrypted at rest with XChaCha20-Poly1305.
</Overview>
<Category>Tools:Utilities Security:</Category>
<WebUI>http://[IP]:[PORT:8710]/</WebUI>
<TemplateURL/>
<Icon>https://gitea.milesward.dev/mward4/Discord-Authenticator/raw/branch/main/crates/webui/src/logo.svg</Icon>
<ExtraParams/>
<PostArgs/>
<CPUset/>
<DateInstalled/>
<DonateText/>
<DonateLink/>
<Requires>
A Discord incoming-webhook URL, from Server Settings → Integrations →
Webhooks → New Webhook → Copy Webhook URL. You can add it during first-run
setup or later in Settings.
</Requires>
<Config Name="WebUI Port" Target="8710" Default="8710" Mode="tcp"
Description="Port for the web UI and API."
Type="Port" Display="always" Required="true" Mask="false">8710</Config>
<Config Name="Data" Target="/var/lib/discord-authenticator"
Default="/mnt/user/appdata/discord-authenticator"
Mode="rw"
Description="Vault, encryption key, admin account and settings. BACK THIS UP — losing it loses every stored TOTP secret."
Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/discord-authenticator</Config>
<Config Name="Master key (optional)" Target="DA_MASTER_KEY" Default=""
Mode=""
Description="Base64 of 32 random bytes. Set this to keep the vault key OFF the appdata share, so a leaked backup of the share is useless on its own. Generate with: openssl rand -base64 32. Leave blank to auto-generate a key file inside the data folder instead. WARNING: if you set this later, existing secrets become unreadable — export them first."
Type="Variable" Display="advanced" Required="false" Mask="true"></Config>
<Config Name="Log filter" Target="DA_LOG" Default="info,da=info"
Mode=""
Description="tracing-subscriber EnvFilter expression. Use info,da=debug for more detail."
Type="Variable" Display="advanced" Required="false" Mask="false">info,da=info</Config>
</Container>