You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I run OpenTAKServer for a first-responder / volunteer team. A common situation: my team shows up to a scene with several people who need to be onboarded onto the server quickly — install ATAK, get a cert enrolled, join the right groups. Today the flow has a lot of steps and a lot of "give me your phone, let me type the URL." It's slow when there's other work to do.
What I'd like: each admin on a tablet, hit a per-user "Enrollment QR" button on the OTS Users page, the new user scans on their EUD, ATAK enrolls. Whole interaction in under a minute, no typing. The admin-only QR (already partially supported in OTS via /api/atak_qr_string) would be the vehicle.
I've built a POC of this workflow and used it in the field on my own deployment to verify the ergonomics. Posting here because I want to make sure I'm not creating unnecessary security holes in the TAK ecosystem before proposing it upstream. If the direction is right, I'll open the PRs against brian7704/OpenTAKServer and brian7704/OpenTAKServer-UI; if not, I'd rather hear it now than after the diffs are written.
What an audit of the current QR token implementation found
While building the POC, I audited the existing /api/atak_qr_string + Token.verify_token path and found a handful of issues anyone building on top of this feature would need to plan around. Listing them here as constraints worth fixing regardless of whether the broader workflow lands:
Unlimited-use tokens by default.generate_token() only adds the JWT max claim when self.max_uses is set, and verify_token() only enforces when the claim is present. A token generated via the API without max can be redeemed forever until someone deletes it manually.
GET returns the existing JWT regardless of state.GET /api/atak_qr_string has no check for total_uses >= max_uses and no check for expired tokens. An admin opening the modal for a user who already enrolled gets handed back the same usable QR.
Consumed rows persist in the DB.total_uses += 1 increments but the row is never deleted, even after max_uses is reached. Combined with the previous point, this means dead-but-undeleted tokens can be re-surfaced.
Defaults are UI-only. Some clients call POST with max=1 and a 15-minute exp; others may not. There's no server-side floor.
No audit trail. Issuing a QR for another user is a sensitive admin action but no log line currently records who issued what for whom.
No rate limiting on the endpoint. Admin role is required, but compromised admin credentials could mint tokens at will undetected.
#311 closes the first three (unlimited-use, stale-GET, persistent-rows), partially addresses the UI-only-defaults gap with a server-side max=1 default, and adds an audit log via logger.info. The full configurability story, the rate-limiting question, and the workflow questions below are open for discussion first.
Open questions
Workflow
Does an in-field QR-based admin enrollment match how you operate? If not, what does your onboarding flow look like, and where does it break down?
Is the existing per-user POST /api/atak_qr_string flow sufficient with better defaults, or does the field use case warrant something more (bulk QR generation, pre-staged tokens for a known roster, an "enrollment booth" mode)?
Self-signup vs admin-issued — for a stranger walking up on scene, would you ever want them to register themselves and then have the admin just hand them a QR, or is admin-creates-the-user-too the right shape?
Defaults & policy
Default TTL — 15 min, longer, or operator-configurable via OTS_QR_TOKEN_DEFAULT_TTL_SECONDS?
Default max_uses — should the server hard-floor at 1, or be configurable for ops that legitimately want N-use (e.g. team-wide one-time bulk enroll)?
Should the lifetime / max defaults live in config.yml or be per-call only?
Users-page UX
All of these are about making it fast to find the right user under field conditions — feedback welcome on whether they'd actually help:
Case-insensitive sort on paginated list endpoints. Today sorting Users by username gives you ALICE, BOB, alice, bob (Postgres default collation) instead of the natural mixed-case order. Field admins scrolling for someone whose case they don't remember will benefit immediately. Already submitted as a small separate fix in fix(api): case-insensitive sort on string columns in paginate helper #312.
Username / email search on the Users page. Right now you scroll or sort; in the field you'd want to find "Smith" in two keystrokes. Worth noting: currently no client-side search exists anywhere in the OTS UI — every page is server-side paginated. So this would be either a ?search= API addition or the UI's first client-side filter.
Email column on the Users page. The backend User model has an email field but the /api/users response doesn't currently expose it; the UI's User interface has no email either. Useful as a secondary search key and identity confirmation.
Token-status badge per row — "Active QR (expires in 12 min)" / "Used" / "—". Lets the admin see at a glance who's mid-enrollment and avoid double-issuing. Reuses the GET endpoint.
Security beyond the audit items above
Rate limiting on /api/atak_qr_string — worth adding Flask-Limiter to the project for this and other sensitive endpoints?
Confirmation prompt on regenerate when the existing token has total_uses > 0 or was issued recently (would invalidate an EUD that's currently enrolling).
Mobile-first modal — field admins use tablets; touch targets, big QR, one-tap copy.
Should max_uses > 1 be deprecated entirely? The current model lets a single QR enroll an arbitrary number of devices. The field-enrollment work treats this as a security smell — every QR represents one device's enrollment, so multi-use is at best a convenience for batch onboarding and at worst a way for a leaked QR to enroll many unauthorized devices. My POC defaults both the new admin per-user flow and the existing self-service Navbar flow to max=1, but the field remains editable. Open question: keep multi-use as a power-user option, hide it behind a non-default config flag, or remove it from the API entirely?
Adjacent capability worth scoping as a fast-follow
Per-device auth-token revocation that doesn't touch the user account. The natural inverse of field enrollment: a device gets lost or compromised on scene, the admin needs to neutralize just that device's access fast, without disabling the user or breaking their other EUDs. The recent EUD cert-revocation work covers the cert path; this would extend the same idea to any other auth artifact bound to a single device (lingering JWT sessions, future per-device API tokens, mission invitation tokens). Worth scoping as its own thread once the enrollment workflow direction is settled — the two are a matched pair operationally.
Ask
Looking for direction before investing in the rest of the implementation:
Is this a workflow other operators want?
What's the right shape — admin-issued per-user (the POC direction) vs something else?
Are the proposed Users-page additions (search, email, status badge) the right scope, or should this stay narrower?
Anything about your own field-enrollment experience that should be in scope but isn't?
On the broader security question: am I missing anything in the TAK ecosystem (CoT trust, federation, plugin auth, etc.) that an admin-issued QR workflow could undermine in ways I haven't thought of?
The scenario
I run OpenTAKServer for a first-responder / volunteer team. A common situation: my team shows up to a scene with several people who need to be onboarded onto the server quickly — install ATAK, get a cert enrolled, join the right groups. Today the flow has a lot of steps and a lot of "give me your phone, let me type the URL." It's slow when there's other work to do.
What I'd like: each admin on a tablet, hit a per-user "Enrollment QR" button on the OTS Users page, the new user scans on their EUD, ATAK enrolls. Whole interaction in under a minute, no typing. The admin-only QR (already partially supported in OTS via
/api/atak_qr_string) would be the vehicle.I've built a POC of this workflow and used it in the field on my own deployment to verify the ergonomics. Posting here because I want to make sure I'm not creating unnecessary security holes in the TAK ecosystem before proposing it upstream. If the direction is right, I'll open the PRs against
brian7704/OpenTAKServerandbrian7704/OpenTAKServer-UI; if not, I'd rather hear it now than after the diffs are written.What an audit of the current QR token implementation found
While building the POC, I audited the existing
/api/atak_qr_string+Token.verify_tokenpath and found a handful of issues anyone building on top of this feature would need to plan around. Listing them here as constraints worth fixing regardless of whether the broader workflow lands:generate_token()only adds the JWTmaxclaim whenself.max_usesis set, andverify_token()only enforces when the claim is present. A token generated via the API withoutmaxcan be redeemed forever until someone deletes it manually.GET /api/atak_qr_stringhas no check fortotal_uses >= max_usesand no check for expired tokens. An admin opening the modal for a user who already enrolled gets handed back the same usable QR.total_uses += 1increments but the row is never deleted, even aftermax_usesis reached. Combined with the previous point, this means dead-but-undeleted tokens can be re-surfaced.max=1and a 15-minute exp; others may not. There's no server-side floor.#311 closes the first three (unlimited-use, stale-GET, persistent-rows), partially addresses the UI-only-defaults gap with a server-side
max=1default, and adds an audit log vialogger.info. The full configurability story, the rate-limiting question, and the workflow questions below are open for discussion first.Open questions
Workflow
/api/atak_qr_stringflow sufficient with better defaults, or does the field use case warrant something more (bulk QR generation, pre-staged tokens for a known roster, an "enrollment booth" mode)?Defaults & policy
OTS_QR_TOKEN_DEFAULT_TTL_SECONDS?max_uses— should the server hard-floor at 1, or be configurable for ops that legitimately want N-use (e.g. team-wide one-time bulk enroll)?config.ymlor be per-call only?Users-page UX
All of these are about making it fast to find the right user under field conditions — feedback welcome on whether they'd actually help:
ALICE, BOB, alice, bob(Postgres default collation) instead of the natural mixed-case order. Field admins scrolling for someone whose case they don't remember will benefit immediately. Already submitted as a small separate fix in fix(api): case-insensitive sort on string columns in paginate helper #312.?search=API addition or the UI's first client-side filter.Usermodel has anemailfield but the/api/usersresponse doesn't currently expose it; the UI'sUserinterface has noemaileither. Useful as a secondary search key and identity confirmation.Security beyond the audit items above
/api/atak_qr_string— worth adding Flask-Limiter to the project for this and other sensitive endpoints?total_uses > 0or was issued recently (would invalidate an EUD that's currently enrolling).max_uses > 1be deprecated entirely? The current model lets a single QR enroll an arbitrary number of devices. The field-enrollment work treats this as a security smell — every QR represents one device's enrollment, so multi-use is at best a convenience for batch onboarding and at worst a way for a leaked QR to enroll many unauthorized devices. My POC defaults both the new admin per-user flow and the existing self-service Navbar flow tomax=1, but the field remains editable. Open question: keep multi-use as a power-user option, hide it behind a non-default config flag, or remove it from the API entirely?Adjacent capability worth scoping as a fast-follow
Per-device auth-token revocation that doesn't touch the user account. The natural inverse of field enrollment: a device gets lost or compromised on scene, the admin needs to neutralize just that device's access fast, without disabling the user or breaking their other EUDs. The recent EUD cert-revocation work covers the cert path; this would extend the same idea to any other auth artifact bound to a single device (lingering JWT sessions, future per-device API tokens, mission invitation tokens). Worth scoping as its own thread once the enrollment workflow direction is settled — the two are a matched pair operationally.
Ask
Looking for direction before investing in the rest of the implementation:
🤖 Generated with Claude Code