Commit Graph

14090 Commits

Author SHA1 Message Date
kolaente 0a7750ee3d feat(api/v2): add Project CRUD on /api/v2
Add a simple /{id} CRUD resource for projects on the Huma-backed /api/v2,
mirroring labels.go. Exposes the expand query param (value "permissions")
which surfaces the caller's max permission per project on both list and read.
The handler stays standard (DoReadAll/DoReadOne/DoCreate/DoUpdate/DoDelete);
the model's ReadOne keeps handling the Favorites pseudo-project and
saved-filter-backed projects.

Self-registers via init() -> AddRouteRegistrar; no routes.go change.
projectusers is intentionally out of scope.
2026-06-05 07:40:07 +00:00
Frederick [Bot] fd2f005a3b chore(i18n): update translations via Crowdin 2026-06-05 00:31:43 +00:00
kolaente af2482aab2 fix(labels): report owner-level max_permission
Label writes/deletes are owner-only (CanUpdate/CanDelete), but hasAccessToLabel
derived max_permission from the accessible task's permission with a read fallback
for the creator branch — so owners showed as read-only and a task-admin reading
a label via that task showed as a label admin. Derive it from ownership instead:
owner -> admin, otherwise read. Corrects the value CanRead returns for both v1's
x-max-permission header and the new v2 max_permission body field.
2026-06-04 21:16:51 +00:00
kolaente e22e169fb9 feat(api/v2): report max_permission on label and project-view reads
Read/update use a per-resource struct that embeds the model by value and adds a
readOnly max_permission field (labelReadBody, projectViewReadBody); Go and Huma
promote the embedded fields, so the body stays flat with no custom marshaler and
nothing on the shared models. The handler passes the model's Updated and the
permission to conditionalReadResponse, which folds the permission into the ETag.
Adds a webtest asserting two callers with different permission on the same label
get different ETags, plus max_permission presence assertions.
2026-06-04 21:16:51 +00:00
kolaente 6836903c5f feat(api/v2): add shared conditional read helper and document list params
conditionalReadResponse applies the If-Match/If-None-Match/If-Modified-Since
precondition (304/412) and returns the shared read envelope. The caller's
permission is folded into the ETag so a share/role change invalidates the cache
even when the model's modified time is unchanged.

Also adds doc: tags to the shared ListParams (q/page/per_page).
2026-06-04 21:16:51 +00:00
kolaente 72445c4d2f docs(agents): comments should document the why, not the what
Adds a Code Style bullet: default to no comment; only document a non-obvious
why (gotcha, invariant, rejected alternative) in one tight line; cut comments
that restate the code, a name, or a signature.
2026-06-04 20:59:45 +00:00
renovate[bot] e39885682c chore(deps): update dev-dependencies 2026-06-04 18:30:29 +00:00
kolaente d604d8d443 test(api/v2): port full v1 TaskDuplicate coverage
Assert the specific domain error code (ErrCodeTaskDoesNotExist) on the
nonexistent-source-task case, matching v1's TestTaskDuplicate. v2 carries
the code as the numeric `code` field of the RFC 9457 problem+json body,
so the test now checks that field instead of only the 404 status.
2026-06-03 20:29:15 +00:00
kolaente c9f8b87263 test(api/v2): port full v1 avatar coverage to TestAvatar
Bring the v2 avatar webtest to 1:1 parity with the v1 avatar tests so
the v1 routes and tests can be removed without losing coverage:

- link-share auth path: a request authenticated as a link-share user
  (not a regular JWT) returns 200 + non-empty image bytes, porting
  v1's TestLinkShareAvatar.
- bot user: the botmarble provider path returns 200 + SVG bytes, a
  distinct rendering v1 never exercised; asserts the marble mask id so
  it cannot silently fall through to the default placeholder.
- non-numeric size: rejected with 422 (Huma's int64 query validation)
  rather than v1's 400 ErrInvalidModel, both being client errors that
  refuse the malformed input.
2026-06-03 19:58:27 +00:00
kolaente 984a2633cc docs(task-comments): trim comments to the non-obvious why
Cut narration a reader can infer from the code (envelope element type,
path-param binding, per-case test descriptions). Keep the non-obvious
rationale: IDOR scoping, RFC 9110 etag quoting, why the feature gate sits
in the registrar, and the author-only fixture crux.
2026-06-03 19:57:26 +00:00
kolaente 88832a3e8b test(api/v2): port full v1 task comment coverage (permission matrix, IDOR, search) 2026-06-03 19:57:26 +00:00
kolaente 4d404e376a test(api/v2): prove author-only comment restriction with a writer non-author
The Forbidden non-author update/delete cases used user6, who also lacks access
to task 1, so they only proved access denial, not the author-only restriction.
Add cases driven by testuser1 against comment 4 on task 16 (project 7): user1
has write access via team 3 but did not author the comment (user6 did), so a
403 there genuinely exercises the authorship branch. Keep the user6 cases as
the no-access negatives, relabelled for clarity.
2026-06-03 19:57:26 +00:00
kolaente 808ef2534e fix(task-comments): derive update event doer from authenticated user
TaskComment.Update used tc.Author as the TaskCommentUpdatedEvent doer, but
that field is bound from the request body. A client could omit it (nil doer,
breaking the event) or spoof another user. Resolve the doer from the session
auth via GetUserOrLinkShareUser instead, mirroring Create and Delete. CanUpdate
already guarantees the authenticated user is the comment's author, so this is
both correct and consistent. Affects v1 and v2, which share the model.
2026-06-03 19:57:26 +00:00
kolaente 3271a1e1af feat(api/v2): add nested task comment CRUD
Add TaskComment CRUD on /api/v2 under /tasks/{task}/comments, mirroring
the project_views nested-resource shape. The resource is feature-gated by
config.ServiceEnableTaskComments, checked inside the registrar so it runs
after config has loaded. Self-registers via init()+AddRouteRegistrar; no
routes.go change. ReadAll exposes the order_by (asc/desc) query param.

Adds doc:/readOnly: tags to the shared TaskComment model fields and a
TestHumaTaskComment webtest covering list/read/create/update/delete plus
negatives (non-author forbidden, comment under the wrong task -> 404).
2026-06-03 19:57:26 +00:00
kolaente 67aca34124 test(api/v2): port full v1 admin projects coverage
Bring TestHumaAdminProjects to 1:1 parity with v1 TestAdmin_ListProjects
by asserting owner hydration ("username":"user1", never "owner":null)
and project field presence ("id":, "title":) on the response body, in
addition to the existing gate personas and ownership/archived visibility
cardinality checks.
2026-06-03 19:48:08 +00:00
kolaente 58bc03d712 test(api/v2): port full v1 project view coverage 2026-06-03 19:46:38 +00:00
kolaente 5c05a1a289 test(api/v2): port full v1 label coverage
Bring the merged v2 Label webtest (TestHumaLabel) to 1:1 parity with the
model-level matrix in pkg/models/label_test.go so the v2 HTTP surface
independently proves the full visibility/permission contract once v1's
routes and tests are removed.

Added scenarios:
- ReadAll asserts the EXACT visible set for user1 = {1,2,4,7,8}, with #3
  (other owner, unattached), #5 (other owner, inaccessible task) and #6
  (GHSA private fixture) explicitly absent — not just contains/not-contains.
- ReadOne: #3 forbidden (other owner, unattached); #6 forbidden (GHSA
  private); #4 ALLOWED (other owner but visible via an accessible task);
  #7 allowed (own, unattached); #8 allowed (own, only on inaccessible task).
- Update/Delete: #4 forbidden (GHSA-hj5c-mhh2-g7jq read-vs-write: readable
  but not writable by the non-owner); #3 forbidden; #6 forbidden.
- Create asserts hex-color normalization (#aabbcc -> aabbcc).

Keeps the existing ETag/304 and merge-patch subtests.
2026-06-03 19:38:57 +00:00
Frederick [Bot] 8dada8b298 [skip ci] Updated swagger docs 2026-06-03 19:23:14 +00:00
kolaente c0392e42ac test(api/v2): port full v1 team coverage (permission matrix, public discovery, exact cardinality, DB persistence) 2026-06-03 18:56:12 +00:00
kolaente cdb1db855b test(api/v2): cover include_public team surfacing and its config gate 2026-06-03 18:56:12 +00:00
kolaente dd32e3e496 fix(api/v2): keep include_public out of the team body schema
include_public is a list-time query flag, not a team field. With json:"include_public" it leaked into the v2 Team request/response body schema (POST/PUT). Mark it json:"-" so it only travels as a query parameter: v1 binds it via the query tag, and the v2 list handler takes it as a dedicated query field and sets it on the model internally.
2026-06-03 18:56:12 +00:00
kolaente 3233dff545 docs(api/v2): mark team external_id read-only 2026-06-03 18:56:12 +00:00
kolaente dab6ac620d feat(api/v2): add team CRUD endpoints
Adds Team CRUD on /api/v2 mirroring the labels reference resource:
list, read, create, update, delete under /teams[/{id}].

- The list op exposes an include_public query param bound onto the
  model so Team.ReadAll can surface public teams (gated by the instance
  public-teams setting).
- Read ops emit an ETag and honor If-None-Match (304).
- Model fields gain doc: tags; server-controlled fields are marked
  readOnly:true.
- Self-registers via init()/AddRouteRegistrar; no routes.go change.
- New webtest TestHumaTeam (named to avoid clashing with the v1 model
  TestTeam) covers list/read/create/update/delete plus negatives
  (non-member 403, nonexistent 403/404) and ETag/304.
2026-06-03 18:56:12 +00:00
kolaente 2cb0fdcded docs(skills): note v2 query params must be direct fields on the handler input
A shared/embedded query-param helper struct silently fails to bind under Huma
when combined with other query params (found implementing Project's expand);
each query param must be a direct field on the operation's input struct.
2026-06-03 18:14:37 +00:00
renovate[bot] 58b2aaa74e chore(deps): update dev-dependencies to v10.9.2 2026-06-03 13:14:33 +00:00
kolaente ceb2b4f161 docs(api/v2): keep registrar godoc attached; clarify registry concurrency + ordering
- Move each resource file's init() below its RegisterXRoutes func so the func doc
  comment stays attached (it was documenting init()).
- Note AddRouteRegistrar is init-only and not concurrency-safe.
- Reword RegisterAll: registrar order is unspecified and irrelevant.
2026-06-03 13:14:13 +00:00
kolaente 5257922f3b docs(skills): document v2 self-registration pattern
Update the api-v2-routes skill: resources self-register via
func init() { AddRouteRegistrar(RegisterFooRoutes) } instead of editing
registerAPIRoutesV2. Note distinct-registrar-name requirement (RegisterAvatarRoutes
collision), config-gating inside the registrar, and AutoPatch being automatic via
RegisterAll. Also flag that mage test:filter injects -short (which skips
pkg/webtests entirely), so run single webtests with go test -run.
2026-06-03 13:14:13 +00:00
kolaente b04d4d269c refactor(api/v2): self-register resource routes via init() registry
Previously every new v2 resource appended an explicit RegisterXRoutes call
(and the EnableAutoPatch line had to stay last) in registerAPIRoutesV2 in
routes.go, causing recurring merge conflicts across in-flight PRs.

Resources now self-register: each resource file calls AddRouteRegistrar from
an init(), and registerAPIRoutesV2 just calls apiv2.RegisterAll, which runs
every registrar and then EnableAutoPatch. New resources touch zero shared
lines.
2026-06-03 13:14:13 +00:00
kolaente 220af19a39 refactor(api/v2): register upload route as RegisterAvatarUploadRoutes
Avoids a duplicate RegisterAvatarRoutes declaration in package apiv2 now that
the avatar GET route (#2818) is on main; both routes are registered distinctly.
2026-06-02 11:55:25 +00:00
kolaente b18e051ab3 fix(api/v2): reject non-decodable images (e.g. SVG) on avatar upload with 400 2026-06-02 11:55:25 +00:00
kolaente d2319e1257 refactor(avatar): share avatar-upload logic between v1 and v2 handlers 2026-06-02 11:55:25 +00:00
kolaente 2f4e3ecb91 fix(api/v2): align avatar upload body limit with global overhead
MaxBodyBytes was set to exactly the configured max file size, but a
multipart request carries extra bytes (boundary, part headers) on top of
the file, so a file at the limit could be rejected by Huma before the
handler runs. Mirror the +2 MB overhead that Echo's global BodyLimit
middleware already allows so a max-sized avatar isn't rejected.
2026-06-02 11:55:25 +00:00
kolaente cfac0773d7 fix(api/v2): accept real image content-types on avatar upload
Browsers set a real image Content-Type (image/png, image/jpeg, ...) on
the multipart avatar part, while programmatic clients often send
application/octet-stream. The part contentType tag is an allow-list for
Huma's MimeTypeValidator, which runs before the handler; broaden it so
both cases are accepted instead of being rejected with a 422.

The byte-level mimetype.DetectReader check in the handler remains the
real security gate and is unchanged.

Extend the webtest with a case that sends a part declared as image/png
and asserts it reaches the handler successfully.
2026-06-02 11:55:25 +00:00
kolaente 782c17c01d feat(api/v2): upload user avatar via multipart
Add PUT /api/v2/user/settings/avatar, the first multipart/form-data file
upload on the Huma-backed v2 API. Reuses v1's byte-level mime validation
(mimetype.DetectReader) and storage (upload.StoreAvatarFile), modeling the
request as a huma.MultipartFormFiles input so it renders as multipart/form-data
in the OpenAPI spec instead of being read off the raw echo context.

Flips the user's avatar provider to "upload" on success. Authenticated (JWT).
2026-06-02 11:55:25 +00:00
kolaente e81ccb3486 refactor(avatar): share avatar resolution between v1 and v2 handlers
Extract the duplicated user-lookup, provider-selection and size-clamping
logic from the v1 GetAvatar and v2 avatarGet handlers into a single
avatar.GetAvatarForUsername helper. Both handlers now call it and keep
only their transport-specific code (v1: echo size parse + c.Blob, v2:
huma input/response). Pure refactor, behavior is unchanged.
2026-06-02 08:17:00 +00:00
kolaente a4a0af91ff feat(api/v2): serve user avatars
Add GET /api/v2/avatar/{username}, the v2 reference for a binary response
modeled in the OpenAPI spec. Reuses the v1 avatar provider logic (provider
lookup, size clamp to config.ServiceMaxAvatarSize, runtime content-type) and
returns raw image bytes via Huma's []byte body + dynamic Content-Type header
idiom, advertised in the spec as application/octet-stream.

The endpoint is authenticated under the global security like every other v2
route (an anonymous request gets a 401); it is not public.
2026-06-02 08:17:00 +00:00
kolaente 774d884f5c test(api/v2): assert admin project id via structured json 2026-06-02 07:38:08 +00:00
kolaente 17bef4f599 test(api/v2): defer license reset in admin webtest 2026-06-02 07:38:08 +00:00
kolaente 730932be13 test(api/v2): defer session close in admin webtest 2026-06-02 07:38:08 +00:00
kolaente 2e8bd6724b fix(api/v2): apply rate limit before the admin gate 2026-06-02 07:38:08 +00:00
kolaente 82ad23c135 feat(api/v2): gate admin routes by feature + instance admin
Add the admin + license gate for /api/v2 and ship the first gated
resource, GET /api/v2/admin/projects (AdminProjectList).

The gate reuses the existing v1 middleware functions unchanged —
RequireFeature(license.FeatureAdminPanel) and RequireInstanceAdmin(),
both of which serve 404 on failure. Rather than splitting the single
v2 Huma API into a separate gated sub-group (which would split the
OpenAPI spec and drop admin operations from /api/v2/openapi.json), the
gate is applied as a path-scoped Echo middleware on the shared /api/v2
group, firing only for /api/v2/admin/* and after the token middleware.
This preserves v1's 404-not-403 semantics and keeps admin routes in the
unified v2 spec and Scalar docs.

AdminProjectList lists every project on the instance (archived
included), behind the gate. Adds doc:/readOnly: tags to the shared
Project model so it documents correctly as a v2 schema.

Tests in pkg/webtests/huma_admin_test.go (TestHumaAdminProjects) cover
all three personas: non-admin -> 404, admin without feature -> 404,
admin with feature -> 200 list, plus unauthenticated -> 401.
2026-06-02 07:38:08 +00:00
bradmartin333 6076102d21 fix(frontend): wrap notifications in Teleport to appear above modals for #2744 2026-06-02 06:30:48 +00:00
renovate[bot] 4fc4125546 chore(deps): update dev-dependencies to v8.60.1 2026-06-02 06:27:20 +00:00
Frederick [Bot] 0f50dc047d [skip ci] Updated swagger docs 2026-06-01 13:22:09 +00:00
kolaente 738bcd0c77 fix(api/v2): scope project view delete to its parent project 2026-06-01 13:04:34 +00:00
kolaente 9858792123 fix(api/v2): guard against nil bucket configuration elements 2026-06-01 13:04:34 +00:00
kolaente 1d7d67541f fix(api/v2): dedupe BucketConfigurationMode enum tag 2026-06-01 13:04:34 +00:00
kolaente 5ddc9d8ff0 feat(api/v2): add project view routes
Add ProjectView CRUD on /api/v2 under the nested path
/projects/{project}/views[/{view}], establishing the two-path-param
binding pattern for sub-resources. Mirrors the labels.go handler shape
and reuses handler.Do* so permission checks stay at the model layer.

Both {project} and {view} are bound on every operation; {project} is
threaded onto ProjectView.ProjectID (ReadOne resolves via
GetProjectViewByIDAndProject, which needs the parent id). List wraps the
[]*models.ProjectView slice in the shared Paginated envelope, read sends
an ETag for If-None-Match/304, and AutoPatch synthesises PATCH.

Also:
- Tag exposed ProjectView / ProjectViewBucketConfiguration / nested
  TaskCollection fields with doc: descriptions; mark server-controlled
  fields (id, project_id, created, updated) readOnly. Safe for v1.
- Give ProjectViewKind and BucketConfigurationModeKind a huma.SchemaProvider
  so the string-serialised enums reflect as string schemas instead of
  Huma's default integer schema (which rejected the string form with 422).

Routes registered in registerAPIRoutesV2 before EnableAutoPatch.
2026-06-01 13:04:34 +00:00
renovate[bot] c7e7f8dca3 chore(deps): update dev-dependencies 2026-06-01 12:30:22 +00:00
Tink 3d6608cac7
feat(api/v2): add task duplicate action (#2815) 2026-06-01 14:13:39 +02:00