Release 2026.24 - #388
Merged
Merged
Release 2026.24#388
Conversation
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
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes #387