Skip to content

Release 2026.24 - #388

Merged
abroa01 merged 19 commits into
mainfrom
development
Jun 11, 2026
Merged

abroa01 merged 19 commits into
mainfrom
development

Conversation

@abroa01

@abroa01 abroa01 commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

closes #387

runleveldev and others added 19 commits May 29, 2026 17:54
Implements a Duo Auth API v2-compatible endpoint surface so Authentik
and standard duo_client libraries can use MIEAuth for push MFA.

- server/duo/: signature (HMAC sig_version 2 & 5), response envelopes,
  Basic-auth + Date-skew verification, user/device resolution, push
  send-and-wait, QR rendering, and 7 endpoint handlers mounted at
  /auth/v2/* (ping, check, preauth, enroll, enroll_status, auth,
  auth_status)
- utils/api/duoIntegrations.js: integration credential collection with
  ikey/skey generation and AES-256-GCM skey encryption at rest
- manage-duo-integrations.js: CLI to manage Duo integrations
- tests/duo.js: signature/auth/response unit tests
- add qrcode dependency for enrollment barcodes
…rations

Extend the Duo compatibility layer with a Duo Admin API (/admin/v1/*) and
make duoIntegrations manageable from the admin dashboard.

- duoIntegrations: add integration `type` (auth|admin) with normalization;
  create/list/regenerate methods and findIntegrationByIkey carry the type.
- auth.authenticateRequest: enforce required integration type (legacy->auth),
  returning INVALID_IKEY on mismatch.
- New server/duo/http.js: shared body/param/CORS/baseUrl helpers; index.js
  refactored to use them.
- New server/duo/adminModel.js: MIEAuth<->Duo user/phone mapping (users,
  DeviceDetails, Invites) including pre-enrollment invite users.
- New server/duo/adminApiV1.js: /admin/v1 mount with regex router and handlers
  for users, phones, and info/summary; requires an admin-type integration.
- response.js: add okPaged/sendOkPaged paginated Admin API envelopes.
- main.js: import the Admin API before the /admin dashboard handler and guard
  that handler so /admin/v1/* sub-paths fall through.
- adminApi.js: REST routes /api/admin/duo/{list,create,set-enabled,
  regenerate,delete} for the dashboard.
- admin dashboard: new Duo tab to create (auth/admin), list, enable/disable,
  rotate, and delete integrations; secret shown once.
- manage-duo-integrations.js CLI: type support.
- tests/duo.js: add okPaged pagination and Admin API object-mapping tests.
The Duo Admin API (/admin/v1/*) previously only logged unhandled
exceptions, so client integration issues (e.g. an Authentik directory
import receiving a 405) were invisible in the server logs.

- Log every incoming request: method, full path, Host, client IP
  (x-forwarded-for aware), and whether an Authorization header was sent.
- Log 404 (no matching route) and 405 (method not allowed) outcomes; the
  405 now also reports and returns the methods that ARE allowed for the
  path and sets a proper `Allow` response header.
- Log authentication failures with the Duo error code and detail (invalid
  ikey / signature / stale Date / wrong integration type) — these were
  previously returned to the client silently.
- Log successful dispatch with the target handler and integration name.
- Log request-body read failures (payload too large / unreadable).
Wrap the lazy-loaded routes in a ChunkLoadErrorBoundary that catches
ChunkLoadError thrown when a build-chunk's hash no longer resolves after
a Meteor hot code push. It auto-reloads once to pull the current bundle
(guarded by a sessionStorage flag to avoid reload loops) and clears the
guard on successful render so future HCPs can self-heal. Also show the
native splash on iOS during reload and use the brand background color.
When existing users register through the Duo enrollment flow with an
auto-approve invite, the PIN was silently discarded. Added
Accounts.setPasswordAsync() call so the PIN is saved as their password.
The /auth/v2/enroll endpoint was setting email=username, which meant
the QR code encoded the username as the email. Now it checks for an
explicit 'email' parameter first, falls back to username only if it
looks like an email address.
buildInviteLockFields was unconditionally locking the email field,
preventing users from entering their email when the invite was created
without one (e.g. Duo enroll with username only).
When the invite was created without an email (e.g. Duo enroll with
username only), the email comparison would always fail because the
user submits their real email but the invite has an empty normalizedEmail.
Now both the registration validation and existing-user conflict check
only enforce email matching when the invite actually stores an email.
Phase 1 watch support via notification mirroring/bridging (no native
watch app):

- iOS: register a static APPROVAL UNNotificationCategory in the push
  init options so Apple Watch renders the Approve/Reject buttons. The
  category id matches aps.category and the action callbacks match the
  existing push.on("approve"/"reject") handlers.
- Android: no change needed; the plugin already emits data.actions as
  NotificationCompat actions (+ WearableExtender) and never sets
  local-only, so Wear OS bridges the buttons automatically.
- Fix sendSecondaryDeviceApprovalRequest to pre-create a
  notificationHistory record and include notificationId/appId so a
  tray/watch-triggered action can reach notifications.handleResponse.
- Extract the approval action contract (category id, action ids, action
  descriptors, required data fields) into utils/constants.js as a single
  source of truth shared by client and server.
- Add payload-shape tests for the iOS category, Android actions, and
  required action data fields.
- Document Watch Support + manual test checklist in README.
…Watch

Apple Watch does not display foreground notification actions for mirrored
notifications because it cannot launch the iPhone app, so it silently hid the
Approve/Reject buttons (leaving only Dismiss). Switch the iOS APPROVAL category
actions to background actions (foreground: false) so they appear on the wrist
and are handled by the existing push.on handlers without opening the app.

Also fix the APNS payload keys (content-available, mutable-content) so the
background push is delivered, and skip the Android-only createChannel call on
iOS to stop the 'createChannel: not defined' error.
feat: add Duo Auth API v2 + Admin API v1 compatibility layer
- tests/main.js: assert iOS APPROVAL category actions use foreground:false
  to match IOS_APPROVAL_CATEGORIES (watchOS hides foreground actions)
- push-notifications.js: only create notification channel on confirmed
  Android; skip when device is undefined
- firebase.js: abort sendSecondaryDeviceApprovalRequest before inserting a
  history record when Firebase is not initialised
feat(watch): Watch Support — approve/reject from Apple Watch & Wear OS
@abroa01 abroa01 linked an issue Jun 11, 2026 that may be closed by this pull request
@abroa01
abroa01 merged commit bd60a63 into main Jun 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release 2026.24

3 participants