Commit graph

322 commits

Author SHA1 Message Date
Alexandre Brandizzi
e941e055b7 Merge remote-tracking branch 'origin/main' into fix/ab/sh-407-cancel-wo-cancels-uplift 2026-09-25 19:49:50 -03:00
Alexandre Brandizzi
a32471d4c0 Serialize sync cancels and vendor uplift requests on the work order lock
The legacy ingest batch holds the per-work-order lock, taken in id order,
for every work order it may cancel until the batch commits. A vendor
uplift request now runs under the same lock. Uplift creation on both
routes refuses a work order cancelled by either lifecycle or status text,
so a request can neither slip past a cancel nor land after one.
2026-09-25 19:37:08 -03:00
Alexandre Brandizzi
498f49a2d8 fix(auth): end earlier sessions when a user's role or account changes
A token carries the user's roles and account, so a demoted admin kept admin
claims until the token expired. The team member update and the admin user
edit now rotate the security stamp and evict the cached value when the role,
account or user name changes. Permission overrides are read per request and
are not in the token.
2026-09-25 19:26:25 -03:00
Alexandre Brandizzi
987ec455f2 Merge remote-tracking branch 'origin/main' into fix/ab/sh-409-invalidate-sessions-on-reset
# Conflicts:
#	Api.SeaHavenIndustries.Tests/AuthenticationServiceTests.cs
#	Api.SeaHavenIndustries.Tests/PasswordPolicyTests.cs
#	SeaHaven.Services/Implementation/AuthenticationService.cs
2026-09-25 19:22:33 -03:00
Alexandre Brandizzi
de8e283ee5
Merge pull request #192 from Sea-Haven-Industries/fix/ab/sh-403-reset-code-hardening
Some checks are pending
Backend CI / Build and test (push) Waiting to run
Backend CI / architecture (push) Waiting to run
Backend CI / review (push) Waiting to run
Backend CI / ci-complete (push) Blocked by required conditions
fix(auth): stop reset-code guessing and email enumeration in Forgot Password
2026-09-25 22:16:01 +00:00
Alexandre Brandizzi
b7be07411e fix(auth): end earlier sessions when a password or account status changes
Tokens now carry a keyed hash of the account's security stamp, and every
authenticated request compares it with the stored stamp (cached for 60 s,
evicted in-process on change). A password reset or change, a deactivation
and a deletion all rotate or remove the stamp, so tokens issued before them
get 401. Tokens without the claim get 401 too.
2026-09-25 19:08:33 -03:00
Alexandre Brandizzi
306ab159cc Cancel pending uplifts when the legacy ingest cancels a work order
The ingest writes only the status text, so the shared helper gains a
status-text form of the same rule and the ingest stages the same
sync-attributed cancellation in its batch save.
2026-09-25 19:01:37 -03:00
Alexandre Brandizzi
99499c3281 Cancel pending uplifts when the CRM cancels a work order
The webhook and reconciliation saves now stage the same pending-uplift
cancellation, with its own sync audit row, as the board cancel. The rule
and the write live in one data-layer helper so the paths cannot drift.
2026-09-25 18:48:44 -03:00
Alexandre Brandizzi
9bad363930 fix(work-orders): cancelling from the board cancels the pending uplift
The board and slide-over cancel a work order through the lifecycle status
patch, which set Canceled without touching uplifts, so a pending uplift
stayed in the approval queue. A patch to Canceled now withdraws pending
uplifts in the same save, each with its own uplift_cancel audit entry, and
runs under the per-work-order gate uplift create uses.
2026-09-25 18:37:56 -03:00
Alexandre Brandizzi
185c84dd8c fix(uplifts): refuse admin revoke of an auto-approved uplift
An admin revoke overturns a human decision, so admins may revoke only
admin-approved uplifts. The work-order revoke path let an admin revoke an
auto-approved uplift they had requested themselves. Both revoke endpoints
now refuse it; dispatchers keep revoking their own auto-approved uplifts.
2026-09-25 18:07:33 -03:00
Alexandre Brandizzi
9198c5cd5d Merge remote-tracking branch 'origin/main' into fix/ab/sh-403-reset-code-hardening 2026-09-25 17:52:04 -03:00
Alexandre Brandizzi
cf32dd2698
Merge pull request #184 from Sea-Haven-Industries/feat/ab/sh-322-overdue-type
Some checks are pending
Backend CI / Build and test (push) Waiting to run
Backend CI / architecture (push) Waiting to run
Backend CI / review (push) Waiting to run
Backend CI / ci-complete (push) Blocked by required conditions
feat(work-orders): add Overdue work order type
2026-09-25 19:52:19 +00:00
Alexandre Brandizzi
900e141bda fix(auth): delete only the checked code and older ones, so a code issued concurrently survives 2026-09-25 16:42:03 -03:00
Alexandre Brandizzi
9084b7f642 fix(team-members): answer invalid_invite when send-code finds the invite closed
SendCodeAsync validates the invite, then starts the code with a conditional
update that also requires the invite to still be open. When an admin revoked
the link (or it was used) between those two reads, the refusal was reported as
resend_too_soon with a Retry-After, although the link was already dead.

On a refused start the invite is now read again: a closed invite gets the same
generic invalid_invite response as any other dead link, and a cooldown or send
limit refusal is computed from the fresh row.
2026-09-25 16:40:54 -03:00
Alexandre Brandizzi
fc3a29f399 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	SeaHaven.Services/Implementation/WorkOrderCompletionService.cs
2026-09-25 16:40:36 -03:00
Alexandre Brandizzi
9bd22e9a74 Merge remote-tracking branch 'origin/main' into fix/ab/sh-403-reset-code-hardening 2026-09-25 13:44:34 -03:00
Alexandre Brandizzi
47060f6a73 fix(work-orders): never return a severity on an Overdue board row
A standalone severity patch on an Overdue WO still stores the value,
because the board patches severity before type when correcting Overdue
to Emergency/Reactive and that write must not be dropped or rejected.
Project the severity as null for Overdue in the board row mapping, which
also feeds the PATCH response, search results and the detail view, so a
stored value never surfaces on a type that carries no severity.
2026-09-25 13:41:19 -03:00
Alexandre Brandizzi
bb41bfd8ed Merge remote-tracking branch 'origin/main' into HEAD 2026-09-25 13:40:06 -03:00
Alexandre Brandizzi
a8d05776a8
Merge pull request #186 from Sea-Haven-Industries/feat/ab/sh-386-password-policy
feat(auth): enforce one password policy on every password-setting path
2026-09-25 16:27:36 +00:00
Alexandre Brandizzi
cc328ce22a fix(team-members): do not report an invite email for a deactivated member 2026-09-25 13:13:50 -03:00
Alexandre Brandizzi
afc2330184 fix(team-members): report only password-rule failures at finish as a rejected password 2026-09-25 13:05:35 -03:00
Alexandre Brandizzi
e53415de39 Merge remote-tracking branch 'origin/feat/ab/sh-386-password-policy' into feat/ab/sh-385-invite-registration 2026-09-25 13:04:18 -03:00
Alexandre Brandizzi
9091335ff2 fix(auth): reject an unconfirmed new password and report only policy failures as weak 2026-09-25 13:02:58 -03:00
Alexandre Brandizzi
b50cd5f5df feat(team-members): report whether the re-invite after an email change was emailed 2026-09-25 12:54:27 -03:00
Alexandre Brandizzi
f491d4c721 fix(team-members): delete invites with their member, re-invite on email change, trust only 2xx SendGrid responses 2026-09-25 12:45:04 -03:00
Alexandre Brandizzi
e09ef061d1 Merge remote-tracking branch 'origin/main' into feat/ab/sh-385-invite-registration
# Conflicts:
#	Api.SeaHavenIndustries/Controllers/TeamMemberController.cs
2026-09-25 12:45:03 -03:00
Alexandre Brandizzi
3249ea4b6f Merge remote-tracking branch 'origin/main' into fix/ab/sh-403-reset-code-hardening 2026-09-25 12:38:22 -03:00
Alexandre Brandizzi
0088cffd47 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	SeaHaven.DataServices/Implementation/LocationDataService.cs
2026-09-25 12:33:37 -03:00
Alexandre Brandizzi
bcc9b6d7a2 fix(auth): invalidate every pending reset code for an email together
Two concurrent first requests can leave two pending codes for one email.
Exhausting or using one now deletes all of them, so a sibling code cannot
become live afterwards.
2026-09-25 12:31:17 -03:00
Alexandre Brandizzi
236199ab7a
Merge pull request #187 from Sea-Haven-Industries/feat/ab/sh-331-sites-api
Sites API: unique site codes, safe delete, open work orders, site notes
2026-09-25 15:26:09 +00:00
Alexandre Brandizzi
c841e130be fix(auth): harden password reset codes against guessing and email enumeration
Forgot Password answers every address the same way and emails a code only
to an active account. Codes are stored as salted SHA-256 hashes, expire 15
minutes after issue, are replaced by a newer request, and are checked only
against the email they were issued to. Five failed checks delete the code;
attempts are reserved with one conditional UPDATE so concurrent guesses
cannot exceed the budget. VerificationCode requires the email, and email and
code are accepted in the JSON body so they stay out of URLs.

The three anonymous endpoints are rate limited to 10 requests per 15
minutes per client IP. Forwarded headers are trusted only through loopback
and private hops, since the API sits behind the EB load balancer and nginx.
The migration adds hash, salt, expiry and attempt columns and deletes the
old plaintext rows.
2026-09-25 12:21:29 -03:00
Alexandre Brandizzi
385229c64d fix(team-members): keep omitted phone, report invite email failures, never echo invite errors 2026-09-25 12:11:52 -03:00
Alexandre Brandizzi
27c21ec32e fix(services): stamp user, contact, calendar and location audit times in UTC
User creation, contact, calendar event and site-contact create/modify/delete stamps used local server time. Validation rules comparing user-entered dates and the JWT expiry are unchanged.
2026-09-25 12:06:36 -03:00
Alexandre Brandizzi
13fec977fa feat(team-members): expose the caller's effective permissions
GET api/team-members/me/permissions returns the keys the signed-in user
holds after role defaults and their own overrides, evaluated by the same
policy that guards writes. The user comes from the token; a missing or
unknown identity gets 401.
2026-09-25 12:03:39 -03:00
Alexandre Brandizzi
c8073123e3
Merge pull request #183 from Sea-Haven-Industries/feat/ab/wo-ids-filter
Some checks are pending
Backend CI / Build and test (push) Waiting to run
Backend CI / architecture (push) Waiting to run
Backend CI / review (push) Waiting to run
Backend CI / ci-complete (push) Blocked by required conditions
feat(workorders): filter board to an exact work-order id set
2026-09-25 14:55:26 +00:00
Alexandre Brandizzi
92dabbfbe3 Hide deleted sites from every location read; require the Add Site fields on create
- Legacy reads (by id, all, by client, paged, address book, exists, count) and the vendor
  preference site check now skip tombstoned sites
- Create requires a client, street address, city, state and at least one complete contact
2026-09-25 11:53:29 -03:00
Alexandre Brandizzi
f9cdbaa06b
Merge pull request #185 from Sea-Haven-Industries/feat/ab/sh-313-completion-templates-api
feat(completion-templates): template content, search, linked work orders and safe delete
2026-09-25 14:49:35 +00:00
Alexandre Brandizzi
c0ae8479ce feat(team-members): invite registration with emailed code confirmation (SH-385) 2026-09-25 11:49:10 -03:00
Alexandre Brandizzi
a224f883bc fix(completion-templates): let PUT clear workOrderType with an explicit null
UpdateAsync only applied WorkOrderType when it had a value, so once a
template was restricted to one work order type no request could make it
trade-generic again. The DTO now records whether workOrderType was present
in the body: omitting it keeps the stored value, an explicit null clears
it, and a concrete value sets it. The templates page echoes the stored
legacy fields on PUT, so its behaviour is unchanged.
2026-09-25 11:40:49 -03:00
Alexandre Brandizzi
d82fb18a3d fix(work-orders): map Overdue to PM catalog in service and template lists 2026-09-25 11:30:34 -03:00
Alexandre Brandizzi
c3865e56ac Sites API: site code uniqueness, soft delete with role check, open work orders, site notes
- Reject duplicate site codes per client (case-insensitive); site code is immutable once set
- Delete tombstones the site and requires the DeleteSites permission (Admin, Scheduler)
- GET /api/locations/{id}/open-work-orders returns the open count and ids
- PATCH /api/locations/{id}/contact-info saves contacts and notes from the work-order Site dialog
- Add nullable Locations.Notes, used as the site-level POC notes fallback
2026-09-25 11:19:29 -03:00
Alexandre Brandizzi
14c8e46dd0 feat(notifications): SEV response-window alerts and breach acknowledgement
Reactive/Emergency work orders with a SEV 1-5 level are timed from their
creation against the SEV Respond deadline (2/4/8/24/72 hours, one backend
table). From 50% they are at risk: a dismissable High row in the "SLA at
Risk" section and an entry in the feed's slaAtRisk set with the server
clock (start, deadline, percent) for the banner and toast. From 100% they
are a Critical acknowledge row that only acknowledging removes.

POST /api/notifications/sla/{id}/acknowledge records who and when as a
work-order audit entry ("SLA breach acknowledged by <name>"), scoped to the
caller's feed audience: 404 outside it, 409 before the deadline, 204 when
recorded or already recorded. A later severity change is a new breach.
2026-09-25 11:16:21 -03:00
Alexandre Brandizzi
66a49ab957 feat(auth): enforce one password policy on every password-setting path (SH-386)
Both hosts now apply the same Identity password rule: at least 6 characters
with one uppercase letter, one number and one special character. Change
password requires an authenticated caller, verifies the current password
before evaluating the new one, and reports a policy rejection separately
from a wrong current password.
2026-09-25 11:06:42 -03:00
Alexandre Brandizzi
7c097c2750 feat(completion-templates): author templates with safety note and ordered procedures
Adds an extra safety note and an ordered procedure list to completion
document templates, name search, creator and last-updated audit fields,
a tenant-scoped count of open work orders that depend on a template, and
a delete that unlinks Services while they keep requiring a document.
Writes are gated by the create/edit/delete completion template team
permissions instead of the Admin role.
2026-09-25 11:00:22 -03:00
Alexandre Brandizzi
b545d4a4fe feat(work-orders): add Overdue work order type
Overdue (8) is a dispatcher-assigned type, separate from the derived
past-due overlay. It takes no severity, resolves services and
completion-doc templates from the PM catalog, and filters as its own
type. The past-due flag now narrows a type filter instead of widening it,
and the dashboard breakdown partitions by stored type.
2026-09-25 10:57:34 -03:00
Alexandre Brandizzi
3019e71093 feat(workorders): filter board search to an exact work-order id set
GET /board/search accepts ids=1,2,3 (positive ints, deduplicated, at most
200). When present the result is exactly those work orders inside the
caller's tenant and base scope; date, status, dispatcher, facet and text
filters are ignored so none of them can hide a listed work order.
Malformed or oversized lists are a 400.
2026-09-25 10:56:47 -03:00
Alexandre Brandizzi
702069f09c test(dashboard): pin drill-down rows for legacy open work orders
The parity test now also asserts which rows the drill-down lists: legacy
open rows (status in Status or in LegacyStatus, or none) are listed and
legacy closed, cancelled or assigned rows are not. Drops ticket keys
from comments.
2026-09-25 03:38:31 -03:00
Alexandre Brandizzi
c49a98db18
Merge branch 'main' into feat/ab/sh-392-dashboard-unassigned 2026-09-25 03:27:33 -03:00
Alexandre Brandizzi
2c8ffaf10e
Merge pull request #173 from Sea-Haven-Industries/fix/ab/sh-383-media-contract
Some checks are pending
Backend CI / Build and test (push) Waiting to run
Backend CI / architecture (push) Waiting to run
Backend CI / review (push) Waiting to run
Backend CI / ci-complete (push) Blocked by required conditions
fix(media): lift the 1 MB proxy body cap and apply the SH-116 media contract
2026-09-25 06:02:21 +00:00
Alexandre Brandizzi
c1ed5dc98d
Merge pull request #181 from Sea-Haven-Industries/fix/ab/sh-400-vendor-readonly-uplifts
Keep work-order uplift requests read-only in the Vendor Portal
2026-09-25 05:58:38 +00:00