Skip to content

Lock down registration-device endpoints and reject activation without an OTP - #1771

Merged
renemadsen merged 2 commits into
stablefrom
feat/registration-device-require-auth
Oct 30, 2026
Merged

renemadsen merged 2 commits into
stablefrom
feat/registration-device-require-auth

Conversation

@renemadsen

Copy link
Copy Markdown
Member

Problem

TimePlanningRegistrationDeviceController had no [Authorize], and the host has no global auth filter. Anyone could:

  • call POST api/time-planning-pn/registration-device/index without logging in and get every tablet's live OTP and the customer number (confirmed on a local dev server);
  • create, update or delete tablets;
  • request a new OTP for any tablet.

With an OTP and the customer number, activate hands out a valid tablet token.

Change

  • Put [Authorize] on the controller.
  • Add [AllowAnonymous] to Activate only. flutter-time calls it (lib/communication/data_helper.dart:38) to pair a tablet before it has a token.

Compatibility

  • flutter-time never calls index, create, update, delete or request-otp.
  • The Angular "Registreringsenheder" page calls them through ApiBaseService, which sends the user's JWT. The page is guarded by AuthGuard only, so the controller uses plain [Authorize] rather than an admin role.
  • gRPC ActivateDevice goes straight to the service, so the MVC attributes don't affect it.

Follow-ups, not in this PR: B2 (anonymous settings/registration-sites with no token), and OTP hardening (B4). See kanban card #5506.

🤖 Generated with Claude Code

TimePlanningRegistrationDeviceController had no [Authorize], so an
anonymous POST to api/time-planning-pn/registration-device/index
returned every tablet's live OTP and the customer number, and the
create/update/delete/request-otp actions were open as well.

Add [Authorize] on the controller and keep only Activate anonymous,
since flutter-time calls it to pair a tablet before it has a token.
The Angular admin page already sends the user's JWT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 05:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are reported for the change protecting device-management endpoints while preserving anonymous tablet pairing.

No open issues 🎉

What changed in this PR

Secures registration-device management endpoints by requiring authentication, while preserving anonymous tablet pairing.

Changes:

  • Adds controller-level [Authorize] to protect device listing, creation, updates, deletion, and OTP requests.
  • Adds [AllowAnonymous] only to Activate, allowing tablets to pair before obtaining a token.
File Description
eFormAPI/​Plugins/​TimePlanning.Pn/​TimePlanning.Pn/​Controllers/​TimePlanningRegistrationDeviceController.cs Requires login for management endpoints while keeping activation anonymous.

Review effort: Balanced

Activate is anonymous and matched x.OtpCode == model.OtCode. A form
post without otCode binds OtCode to null, which EF turns into
OtpCode IS NULL, and activation itself clears OtpCode. So a request
carrying only the customer number matched an already-paired tablet,
reissued its credential and returned it, logging the real tablet out.

Reject an empty OtCode up front and only match devices that are
waiting for pairing (OtpEnabled). Adds RegistrationDeviceActivateTests
and lists it in both CI shard filters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 06:00
@renemadsen renemadsen changed the title Require login on registration-device admin endpoints Lock down registration-device endpoints and reject activation without an OTP Oct 30, 2026
@renemadsen

Copy link
Copy Markdown
Member Author

Added a second commit. Activate stays anonymous, but when otCode was omitted it matched OtpCode == null. So a request with only the customer number could take over the credential of a tablet that was already paired. It now rejects an empty OTP and only matches devices with OtpEnabled. A regression test, RegistrationDeviceActivateTests, is added; its null case fails on the old code.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The test setup explicitly invokes the inherited NUnit setup a second time, duplicating database initialization and SDK resets while overwriting an undisposed DbContext.

1 open issue

Review effort: Balanced

[SetUp]
public async Task SetUp()
{
await base.Setup();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct that the base [SetUp] then runs twice. This class follows the existing convention, though: 47 test classes in this project call await base.Setup() from their own SetUp. Changing only this one would make it inconsistent. The double initialisation applies to the whole suite, so it should be cleaned up across all classes in a separate change, not in this security fix.

@renemadsen
renemadsen merged commit 37642ee into stable Oct 30, 2026
41 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.

2 participants