feat(users): GET /v4/users/by-identity + projectName on signatures (EasyCLA M1) - #5126
feat(users): GET /v4/users/by-identity + projectName on signatures (EasyCLA M1)#5126mlehotskylf wants to merge 2 commits into
Conversation
Supports the EasyCLA → LFX Self Serve M1 "My CLAs" integration (linuxfoundation/lfx-self-serve#1203) by giving Self Serve the two upstream reads it needs for complete identity resolution and project display. GET /v4/users/by-identity (swagger cla.v2.yaml; handler v2/users): - Resolves the union of EasyCLA user records matching ANY of lfUsername, verified email(s), or linked GitHub numeric ID(s); deduped by user_id; empty array on no match. - Union logic added to the v1 users.Service (GetUsersByIdentity) reusing the existing GSI-backed lookups (lf-username-index, lf-email-index, github-id-index) — no table scans. Email match is against lf_email only; a match present solely in a user's secondary user_emails is intentionally not resolved (documented; would require a scan). - The v2 handler is thin translation (v1→v2 model copy). Path is not in the cla-service public allow-list, so it lands on the secured gateway router; Self Serve remains the authorization boundary. - 6 unit tests for union / dedupe / skip-on-error / blank-key / empty-input. projectName on the signature response: - Add projectName to swagger/common/signature.yaml (v1+v2 models). - GET /v4/signatures/user/{userID} enriches each signature's ProjectName from its CLA Group (cached per distinct project ID for the request; best-effort — a lookup miss leaves it empty and never fails the listing). Lets Self Serve show the project name instead of the raw CLA Group ID. make fmt/build/test/lint all green. gen/ is gitignored (regenerate via make swagger). Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
WalkthroughAdds a v2 users-by-identity endpoint backed by normalized, deduplicated lookups across usernames, emails, and GitHub IDs. Signature responses now include resolvable CLA Group display names through cached enrichment. ChangesIdentity Lookup API
Signature Project Name Enrichment
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant UsersHandler
participant UsersService
Client->>UsersHandler: GET /users/by-identity
UsersHandler->>UsersService: GetUsersByIdentity(...)
UsersService->>UsersService: Query username, emails, and GitHub IDs
UsersService-->>UsersHandler: Deduplicated user list
UsersHandler-->>Client: OK response with v2 users
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Adds identity-based user resolution and CLA-group display names for the Self Serve “My CLAs” integration.
Changes:
- Adds the secured
/v4/users/by-identityendpoint with GSI-backed union and deduplication. - Enriches user signatures with cached CLA-group names.
- Adds service tests and updates wiring and mocks.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
cla-backend-go/v2/users/handlers.go |
Implements v2 endpoint translation. |
cla-backend-go/v2/signatures/mock_users/mock_service.go |
Regenerates the users-service mock. |
cla-backend-go/v2/signatures/handlers.go |
Adds signature project-name enrichment. |
cla-backend-go/users/service.go |
Implements identity union logic. |
cla-backend-go/users/service_identity_test.go |
Tests identity resolution behavior. |
cla-backend-go/swagger/common/signature.yaml |
Defines projectName. |
cla-backend-go/swagger/cla.v2.yaml |
Defines the identity endpoint. |
cla-backend-go/cmd/server.go |
Registers the v2 users handler. |
cla-backend-go/cmd/refresh_stored_username_test.go |
Updates the users-service test stub. |
Files not reviewed (1)
- cla-backend-go/v2/signatures/mock_users/mock_service.go: Generated file
… union) Wires the My CLAs server to the new upstream endpoint (linuxfoundation/easycla#5126): - resolveIdentity now calls GET /v4/users/by-identity with the three keys (lfUsername + verified emails + linked GitHub numeric IDs), unioning all matched EasyCLA user records — replacing the interim username-only lookup. Falls back to /v3/users/username/{userName} if by-identity 404s, so the feature still works in environments where it is not yet deployed. - toMyClaAgreement now prefers the upstream-resolved projectName, falling back to the CLA Group ID only when the name is absent. - EasyClaSignature gains an optional projectName field. Tests updated to the array-returning by-identity shape; added cases for the union, the query-param wiring, the username-only fallback, and the projectName-preference. 35 tests pass; tsc + eslint clean. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
cla-backend-go/swagger/cla.v2.yaml (1)
2775-2790: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winBound the
githubIdarrays to avoid request amplification.Each array element triggers a separate GSI lookup in
GetUsersByIdentity(service layer). Without amaxItemscap, a caller can submit an arbitrarily large array and multiply downstream DynamoDB calls per request.♻️ Proposed fix
- name: email description: verified email address(es) to match; repeatable in: query type: array items: type: string collectionFormat: multi required: false + maxItems: 25 - name: githubId description: linked GitHub numeric ID(s) to match; repeatable in: query type: array items: type: string collectionFormat: multi required: false + maxItems: 25🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cla-backend-go/swagger/cla.v2.yaml` around lines 2775 - 2790, Update the email and githubId array parameter definitions in the Swagger schema to include a maxItems constraint, using the service layer’s supported lookup bound for both fields. Keep their existing repeatable multi-value query behavior unchanged.cla-backend-go/v2/users/handlers.go (1)
24-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider a handler-level unit test for the new Configure wiring.
Only service-level tests (
service_identity_test.go) exist; the v2→v1 mapping, empty-result path, and error responses (GetUsersByIdentityInternalServerError) in this handler aren't directly exercised.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cla-backend-go/v2/users/handlers.go` around lines 24 - 61, Add handler-level unit tests for Configure and its GetUsersByIdentityHandler wiring, covering v2-to-v1 user mapping, empty service results, and service or copier failures returning GetUsersByIdentityInternalServerError responses with the expected request ID and error payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cla-backend-go/swagger/common/signature.yaml`:
- Around line 82-84: Add x-omitempty: false to the projectName schema property
in signature.yaml so unresolved signatures serialize projectName as an empty
string instead of omitting the field.
In `@cla-backend-go/users/service.go`:
- Around line 208-213: The GetUsersByIdentity logging must not emit raw identity
values. Remove or replace the lfUsername field and per-item Debugf calls at the
referenced points with non-PII information such as counts or consistent hashes,
preserving useful debugging context without logging email addresses or other raw
identifiers.
- Around line 232-250: Remove the GetUserByEmail call from this email loop and
retain the GetUsersByLFEmail lookup, including its existing error handling and
add() iteration. Leave the email normalization and deduplication behavior
unchanged.
---
Nitpick comments:
In `@cla-backend-go/swagger/cla.v2.yaml`:
- Around line 2775-2790: Update the email and githubId array parameter
definitions in the Swagger schema to include a maxItems constraint, using the
service layer’s supported lookup bound for both fields. Keep their existing
repeatable multi-value query behavior unchanged.
In `@cla-backend-go/v2/users/handlers.go`:
- Around line 24-61: Add handler-level unit tests for Configure and its
GetUsersByIdentityHandler wiring, covering v2-to-v1 user mapping, empty service
results, and service or copier failures returning
GetUsersByIdentityInternalServerError responses with the expected request ID and
error payload.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bb9da78f-0141-4a3b-9903-d3798695217d
📒 Files selected for processing (9)
cla-backend-go/cmd/refresh_stored_username_test.gocla-backend-go/cmd/server.gocla-backend-go/swagger/cla.v2.yamlcla-backend-go/swagger/common/signature.yamlcla-backend-go/users/service.gocla-backend-go/users/service_identity_test.gocla-backend-go/v2/signatures/handlers.gocla-backend-go/v2/signatures/mock_users/mock_service.gocla-backend-go/v2/users/handlers.go
… union) Wires the My CLAs server to the new upstream endpoint (linuxfoundation/easycla#5126): - resolveIdentity now calls GET /v4/users/by-identity with the three keys (lfUsername + verified emails + linked GitHub numeric IDs), unioning all matched EasyCLA user records — replacing the interim username-only lookup. Falls back to /v3/users/username/{userName} if by-identity 404s, so the feature still works in environments where it is not yet deployed. - toMyClaAgreement now prefers the upstream-resolved projectName, falling back to the CLA Group ID only when the name is absent. - EasyClaSignature gains an optional projectName field. Tests updated to the array-returning by-identity shape; added cases for the union, the query-param wiring, the username-only fallback, and the projectName-preference. 35 tests pass; tsc + eslint clean. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
Address review comments from copilot[bot], coderabbitai: - users/service.go: drop the redundant GetUserByEmail call in GetUsersByIdentity — it queries the same lf-email-index as GetUsersByLFEmail (identical key/index/projection, first-result-only), so calling both doubled DynamoDB reads per email for zero added coverage (per copilot[bot], coderabbitai) - users/service.go: trim whitespace-only lfUsername before the GSI lookup, matching the existing email/githubID handling (per copilot[bot]) - users/service.go: keep PII (LF username, raw emails) out of the persistent log fields and per-key miss logs; record counts only (per coderabbitai) - users/service_identity_test.go: update expectations for the removed GetUserByEmail call and cover whitespace-only lfUsername skipping Resolves 3 review threads. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cla-backend-go/users/service.go (1)
233-241: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick winDeduplicate normalized keys before querying the repository.
adddeduplicates returned users, but duplicate or normalization-equivalent emails and GitHub IDs still trigger repeated sequential repository reads. Deduplicate normalized keys before each loop to avoid unnecessary DynamoDB I/O and latency.Proposed fix
+ seenEmails := make(map[string]struct{}, len(emails)) for _, email := range emails { email = strings.ToLower(strings.TrimSpace(email)) if email == "" { continue } + if _, seen := seenEmails[email]; seen { + continue + } + seenEmails[email] = struct{}{} + seenGitHubIDs := make(map[string]struct{}, len(githubIDs)) for _, githubID := range githubIDs { githubID = strings.TrimSpace(githubID) if githubID == "" { continue } + if _, seen := seenGitHubIDs[githubID]; seen { + continue + } + seenGitHubIDs[githubID] = struct{}{}Also applies to: 249-255
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cla-backend-go/users/service.go` around lines 233 - 241, Deduplicate the normalized email keys before the email loop in the user lookup flow, and likewise deduplicate normalized GitHub ID keys before the corresponding loop. Update the logic around GetUsersByLFEmail and the analogous GitHub-ID repository query so each unique key is queried at most once while preserving the existing user aggregation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@cla-backend-go/users/service.go`:
- Around line 233-241: Deduplicate the normalized email keys before the email
loop in the user lookup flow, and likewise deduplicate normalized GitHub ID keys
before the corresponding loop. Update the logic around GetUsersByLFEmail and the
analogous GitHub-ID repository query so each unique key is queried at most once
while preserving the existing user aggregation behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7411fe78-89eb-4275-88fa-5df2c9244f49
📒 Files selected for processing (2)
cla-backend-go/users/service.gocla-backend-go/users/service_identity_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- cla-backend-go/users/service_identity_test.go
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- cla-backend-go/v2/signatures/mock_users/mock_service.go: Generated file
Comments suppressed due to low confidence (7)
cla-backend-go/users/service.go:206
- The implementation below treats every repository error as a normal miss.
GetUserByLFUserNameonly returns errors for expression/DynamoDB/unmarshal failures, while the email and GitHub methods mix not-found errors with operational failures. A throttled or unavailable GSI can therefore produce a successful but incomplete/empty response, and the handler's 500 path is effectively unreachable. Skip only recognized not-found results and propagate operational failures so callers can retry instead of silently losing CLA history.
// A lookup that fails or finds nothing for one key is logged and skipped, not fatal: this is a
// "match any" resolver, so one missing key must not fail the others. Returns an empty (non-nil)
// slice when nothing matches.
cla-backend-go/v2/users/handlers.go:33
- This field persists the supplied LF username in every handler log even though
users/service.go:208-209explicitly classifies identity values as PII and avoids logging them. Record only whether the key was supplied, as is already done for the array parameters.
"lfUsername": utils.StringValue(params.LfUsername),
cla-backend-go/swagger/cla.v2.yaml:2781
- These identity values are placed in the query string, while
cmd/server.go:908logsr.URL.String()for every request. Verified emails, LF usernames, and GitHub IDs will therefore be persisted in application logs regardless of the handler's own structured fields. Redact these query parameters in request logging (and verify gateway access-log redaction), or use a request body for this internal lookup.
- name: email
description: verified email address(es) to match; repeatable
in: query
type: array
items:
type: string
collectionFormat: multi
cla-backend-go/users/service.go:240
- The linked M1 contract and FR-005 require resolving verified emails, including matches in
user_emails, but this deliberately queries only primarylf_email. A user whose verified address exists only in the secondary list will miss that EasyCLA record and its CLA history, so the integration cannot meet its stated completeness criterion. Add an indexed secondary-email lookup, or update the linked contract/acceptance criteria to explicitly accept this limitation before merging.
// lf-email-index is keyed on lf_email; GetUsersByLFEmail queries that GSI (no scan) and
// returns every match, so it fully covers GetUserByEmail (same query, first-result-only).
if us, err := s.repo.GetUsersByLFEmail(email); err != nil {
cla-backend-go/v2/signatures/handlers.go:711
claGroupService.GetCLAGroupByIDalways requestsLoadRepoDetails(project/service/service.go:78-105), which launches GitHub and Gerrit repository lookups in addition to DynamoDB (project/repository/repository.go:875-905). Doing that once per distinct signature project is unnecessary for a name-only enrichment and can substantially increase latency and failure exposure. Use the already-injected repository withDontLoadRepoDetails.
if claGroupModel, cgErr := claGroupService.GetCLAGroupByID(ctx, sig.ProjectID); cgErr != nil || claGroupModel == nil {
cla-backend-go/swagger/cla.v2.yaml:2781
- This repeatable array has no
maxItems, so one authenticated request can trigger an unbounded number of sequential DynamoDB email-index queries inGetUsersByIdentity. Add a small contract-level limit (and ideally deduplicate normalized values before lookup) to bound Lambda duration and read cost.
type: array
items:
type: string
collectionFormat: multi
cla-backend-go/swagger/cla.v2.yaml:2789
- This array is also unbounded, and each value causes a sequential GitHub-ID GSI query. Add
maxItemsplus a numeric item pattern/length bound so malformed or oversized requests are rejected before consuming arbitrary DynamoDB reads.
type: array
items:
type: string
collectionFormat: multi
Review Feedback AddressedCommit: e804728 Changes Made
Declined
Threads Resolved5 of 5 unresolved threads addressed. |
… union) Wires the My CLAs server to the new upstream endpoint (linuxfoundation/easycla#5126): - resolveIdentity now calls GET /v4/users/by-identity with the three keys (lfUsername + verified emails + linked GitHub numeric IDs), unioning all matched EasyCLA user records — replacing the interim username-only lookup. Falls back to /v3/users/username/{userName} if by-identity 404s, so the feature still works in environments where it is not yet deployed. - toMyClaAgreement now prefers the upstream-resolved projectName, falling back to the CLA Group ID only when the name is absent. - EasyClaSignature gains an optional projectName field. Tests updated to the array-returning by-identity shape; added cases for the union, the query-param wiring, the username-only fallback, and the projectName-preference. 35 tests pass; tsc + eslint clean. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
|
We no longer need this as @lukaszgryglicki implemented this endpoint and this was only for testing. |
…"My CLAs" feature) (#1226) * feat(cla): read-only My CLAs server slice (EasyCLA M1) Adds the Me-lens "My CLAs" backend for Milestone 1 of the EasyCLA → Self Serve integration: a read-only list of a user's signed ICLAs/ECLAs plus short-lived ICLA PDF-URL resolution, calling the existing EasyCLA v3/v4 APIs through lfx-gateway /cla-service. - shared: MyClaAgreement / MyClasResponse / PdfUrlResponse interfaces - server/types: upstream EasyCLA signature/user shapes + ResolvedClaIdentity - server/services/cla.service.ts: username-only identity resolution via /v3/users/username/{userName} (interim path; three-key union lands with GET /v4/users/by-identity), paginated user-signatures fetch, signed-document URL, and pure classification/status helpers - server/controllers + routes: GET /api/me/clas and GET /api/me/clas/:signatureId/pdf-url; identity derived strictly from the session (SS is the authz boundary), PDF handler returns 404 (never 403) for unknown/not-owned/ECLA signatures Uses the standard gatewayFetch + req.apiGatewayToken pattern; base URL derived from API_GW_AUDIENCE (no new env). Feature gating is applied Angular-side. Ref: specs/001-easycla-ss-integration-fable/m1-my-cla (T012, T016-T018) Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * test(cla): unit tests for My CLAs service + controller (M1) - cla.service.spec.ts (T013/T014): claType classification with fallback heuristic, status derivation incl. superseded, ECLA-invalid filtering, dedupe + signedOn-desc sort, normalizeGithubId (prefixed vs bare), and identity resolution (username match, 404 no-match, github-link detection). - clas.controller.spec.ts (T015): 401 without session, session-derived identity only (request-supplied ids ignored), upstream errors forwarded to next(), and the PDF authz boundary — 404 (never 403) for unknown, not-owned, and ECLA signatures; presigned URL only for owned ICLAs. 32 tests, all passing. Ref: specs/001-easycla-ss-integration-fable/m1-my-cla (T013-T015) Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * feat(cla): My CLAs Profile tab UI (EasyCLA M1, T020-T022) Adds the read-only "My CLAs" Angular view under the Me lens Profile hub, dark-launched behind the `my-clas-enabled` flag: - profile.routes.ts: `profile/clas` route gated by a myClasEnabledGuard (CanMatch); profile-layout appends the "My CLAs" tab (before Transactions) only when the flag is on, keeping tab + route in lockstep. - my-clas.service.ts: client for GET /api/me/clas and the ICLA pdf-url endpoint. - profile-clas.component: ICLA/ECLA sections, loading/error(retry)/empty states, "history may be incomplete" hint on unmatched identity, GitHub-link CTA into the identities flow, and a "Sign a new CLA" link-out to the Contributor Console. PDF download opens a blank tab synchronously (popup-safe) then sets its URL. - agreement-card.component: per-row kind badge, status label (ICLA), signed date, and Download PDF for ICLAs / "Covered by Corporate CLA (CCLA)" for ECLAs. - environments: contributorConsole URL per env (host TBD — see TODO). - feature-flags: MY_CLAS_ENABLED_FLAG constant. Also folds in a small test-fixture fix in cla.service.spec.ts (drop a field that isn't on EasyClaSignature; assert lfUsername instead of a non-existent field) so the strict-template build is green. ng build (strict templates) clean apart from a pre-existing unrelated error; 32 server unit tests pass. Ref: specs/001-easycla-ss-integration-fable/m1-my-cla (T020-T022) Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * fix(cla): use confirmed Contributor Console URLs per environment EasyCLA team confirmed the sign-out targets: - dev/local: https://easycla.dev.communitybridge.org/ - staging: https://easycla.staging.communitybridge.org/ - prod: https://contributor.easycla.lfx.linuxfoundation.org/ Removes the placeholder dev/staging hosts and the confirm-with-team TODOs. Ref: specs/001-easycla-ss-integration-fable/m1-my-cla Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * feat(cla): consume /v4/users/by-identity + projectName (full identity union) Wires the My CLAs server to the new upstream endpoint (linuxfoundation/easycla#5126): - resolveIdentity now calls GET /v4/users/by-identity with the three keys (lfUsername + verified emails + linked GitHub numeric IDs), unioning all matched EasyCLA user records — replacing the interim username-only lookup. Falls back to /v3/users/username/{userName} if by-identity 404s, so the feature still works in environments where it is not yet deployed. - toMyClaAgreement now prefers the upstream-resolved projectName, falling back to the CLA Group ID only when the name is absent. - EasyClaSignature gains an optional projectName field. Tests updated to the array-returning by-identity shape; added cases for the union, the query-param wiring, the username-only fallback, and the projectName-preference. 35 tests pass; tsc + eslint clean. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * test(cla): extract My CLAs view logic to tested pure helpers (T024) The repo has no Angular component-test harness (vitest is server/shared only, no TestBed), so component specs would mean introducing infra no other component uses. Instead, extract the components' branching logic into framework-free helpers in @lfx-one/shared and unit-test those: - cla-view.utils.ts: splitAgreementsByKind, isMyClasEmpty, shouldShowGithubCta, claKindSeverity, claStatusLabel, claStatusSeverity. - profile-clas + agreement-card now delegate to these helpers (no behaviour change; thinner computeds). - 13 unit tests covering the ICLA/ECLA split, empty-vs-loading-vs-error, CTA-visibility rules, and status/kind label+severity mapping. Full suites: 121 server + 366 shared tests pass; ng build clean. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * fix(cla): keep window handle for PDF download popup (PR #1203 review) Address review feedback from copilot[bot]: - profile-clas.component.ts: window.open('', '_blank', 'noopener,noreferrer') returns null in modern browsers, so the popup-safe download always fell through to same-tab navigation and could orphan a blank tab. Open without `noopener` to retain the handle and sever `tab.opener` manually for the same reverse-tabnabbing protection. Resolves 1 review thread. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * style(cla): apply prettier formatting (PR #1203 Code Quality Checks) Resolves the "Check formatting" failure — prettier --write on the four flagged files. Formatting-only, no behaviour change. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * fix(review): address PR #1203 review feedback Address review comment from cursor[bot]: - shared/utils/cla-view.utils.ts: add buildProfileTabs(myClasEnabled) helper that appends the read-only "My CLAs" tab before Transactions when the flag is on - sidebar.component.ts: bind the me-lens ⋯ profile menu to buildProfileTabs() instead of the static PROFILE_TABS, so the sidebar entry point surfaces My CLAs whenever the flag is on — keeping it in sync with the profile-layout subtab strip (per cursor[bot]) - profile-layout.component.ts: use the shared buildProfileTabs helper in place of the inline insert logic (single source of truth) - cla-view.utils.spec.ts: cover buildProfileTabs (flag on/off, insert position, no mutation of the shared constant) Resolves 1 review thread. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * feat(cla): consume EasyCLA /v4/my-clas endpoints for My CLAs Switch the read-only "My CLAs" server module from client-side signature composition (GET /v4/users/by-identity + GET /v4/signatures/user + local classify/filter/dedupe) to the EasyCLA-composed endpoints merged in linuxfoundation/easycla#5125 / #5128: - getMyClas -> single GET /v4/my-clas; upstream computes validity against the current CCLA approval lists and returns classified/deduped/sorted ICLAs+ECLAs, mapped 1:1 to the UI view model. - PDF -> GET /v4/my-clas/{signatureID}/pdf; ownership + ICLA eligibility is enforced upstream (404 on unknown/not-owned/ECLA), so the SS-side re-resolve pre-check is removed. resolveIdentity now sends both githubId and githubUsername: the numeric id validates upstream against EasyCLA records, the username validates via the platform user-service, which is the only path that authorizes a pre-LFID GitHub-only signer whose id has no EasyCLA LFID record. The linked-identity (GitHub) lookup is best-effort: an auth-service/NATS failure degrades to LF-username + email resolution instead of failing the whole page. Response contract to the frontend is unchanged; the superseded status label is no longer produced (the endpoint does not expose the current version). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * feat(cla): remove "Sign a new CLA" button from My CLAs header Drops the Contributor Console link button (and the now-unused contributorConsoleUrl property + environment import) from the My CLAs Profile tab header per design. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * fix(clas): correct impersonation token + ICLA PDF-unavailable label (PR #1226) Address review feedback from copilot[bot], coderabbitai[bot], cursor[bot]: - cla.service.ts: pass the target user's bearerToken on /v4/my-clas and /v4/my-clas/{id}/pdf during impersonation, so upstream authorization runs under the impersonated principal instead of the impersonator's apiGatewayToken (matches the EnrollmentService pattern). Adds coverage. - agreement-card.component.html: only show "Covered by Corporate CLA (CCLA)" for ECLAs; an ICLA with no stored PDF now shows a neutral "PDF unavailable" instead of a false CCLA-coverage statement. - clas.controller.ts: correct the trust-model comment — EasyCLA (not SS) is the ownership authorization boundary; SS only asserts trusted identity keys. Style/convention and speculative suggestions left as-is (see PR thread replies). Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> * feat(profile): render my clas as a single lfx-table (no status column) (#1269) * feat(profile): render My CLAs as a single lfx-table (no status column) Rework the My CLAs profile tab from grouped ICLA/ECLA cards to a single house lfx-table (Project / Type / Signed / Document) with an always-on identity-linking info banner, an lfx-empty-state-based error state, and lfx-table's built-in loading skeleton. Match the approved M1 mockup and reuse the shared lfx-table instead of a bespoke card/row component. - Reworked profile-clas.component.html/.ts to render a single lfx-table - Added an always-on identity-linking info banner (links to /profile/identities) - Added an lfx-empty-state-based error state and table-shaped loading skeleton - Deleted the now-unused agreement-card component - Status column intentionally omitted per the mockup; claStatusLabel/ claStatusSeverity/ClaStatus remain in cla-view.utils.ts as deliberate deadcode pending design confirmation on #1162 Closes #1162 Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org> * fix(clas): show only valid CLAs (ICLA + ECLA) in My CLAs list Filter /v4/my-clas to `valid === true` for both ICLA and ECLA in the BFF (getMyClas), so every row rendered in the My CLAs table is currently valid. This makes the approved no-status mockup correct (closes D1) and adds the previously-missing consumer-side ECLA valid-only display filter (FR-002). The endpoint intentionally returns invalid rows too (valid=false) and leaves the final display filter to the consumer; the endpoint owner endorsed skipping `valid != true` records. Trade-off: invalid/superseded ICLAs are now hidden, which deviates from #1158's literal "ICLAs in all statuses" wording -- flagged to design for a courtesy confirm. Flip the pass-through unit test to assert filtering and revise the component JSDoc to valid-only. Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org> --------- Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org> * fix(clas): skip My CLAs fetch during SSR to avoid a false error flash ProfileClasComponent kicked off getMyClas() during SSR with no browser guard. The server's HTTP call doesn't carry the user's session cookie reliably, so it tends to fail and bakes a false "Couldn't load your CLAs" error into the SSR HTML -- a red-banner flash on hydration before the browser's authenticated fetch resolves. Guard the fetch with isPlatformBrowser and return a not-loaded state on the server, mirroring the sibling ProfileIdentitiesComponent and .claude/rules/ ssr-safety.md. The browser re-runs the fetch on hydration. Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org> * feat(cla): surface salesforce project name and logo in my clas The EasyCLA backend now resolves the Salesforce project name and logo for each signed CLA, so consume the newly-added projectName/projectLogo from the /v4/my-clas endpoint across the My CLAs stack instead of showing only the CLA-group name. - Add projectName/projectLogo to the raw type (cla.types.ts) and the shared UI interface (cla.interface.ts), renaming the pre-existing projectName (which was actually the CLA-group name) to claGroupName - Map the new fields in cla.service.ts, normalizing empty upstream strings to undefined so template fallbacks trigger correctly - Render the Salesforce project logo (fa-file-signature fallback) and the project name (claGroupName as subtext / fallback) in the Project cell of profile-clas.component.html Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org> * refactor(cla): move MyClasState interface to shared Relocate the MyClasState view-state interface out of the profile-clas component file into @lfx-one/shared/interfaces (cla.interface.ts), per the project rule that all interfaces — including component-only view types — live in the shared barrel rather than local to a component. Switch the component's remaining type-only imports (MyClaAgreement, MyClasState) to import type. Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org> --------- Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org> Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org> Signed-off-by: GitHub <noreply@github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* feat(cla): read-only My CLAs server slice (EasyCLA M1)
Adds the Me-lens "My CLAs" backend for Milestone 1 of the EasyCLA → Self
Serve integration: a read-only list of a user's signed ICLAs/ECLAs plus
short-lived ICLA PDF-URL resolution, calling the existing EasyCLA v3/v4
APIs through lfx-gateway /cla-service.
- shared: MyClaAgreement / MyClasResponse / PdfUrlResponse interfaces
- server/types: upstream EasyCLA signature/user shapes + ResolvedClaIdentity
- server/services/cla.service.ts: username-only identity resolution via
/v3/users/username/{userName} (interim path; three-key union lands with
GET /v4/users/by-identity), paginated user-signatures fetch, signed-document
URL, and pure classification/status helpers
- server/controllers + routes: GET /api/me/clas and
GET /api/me/clas/:signatureId/pdf-url; identity derived strictly from the
session (SS is the authz boundary), PDF handler returns 404 (never 403) for
unknown/not-owned/ECLA signatures
Uses the standard gatewayFetch + req.apiGatewayToken pattern; base URL derived
from API_GW_AUDIENCE (no new env). Feature gating is applied Angular-side.
Ref: specs/001-easycla-ss-integration-fable/m1-my-cla (T012, T016-T018)
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* test(cla): unit tests for My CLAs service + controller (M1)
- cla.service.spec.ts (T013/T014): claType classification with fallback
heuristic, status derivation incl. superseded, ECLA-invalid filtering,
dedupe + signedOn-desc sort, normalizeGithubId (prefixed vs bare), and
identity resolution (username match, 404 no-match, github-link detection).
- clas.controller.spec.ts (T015): 401 without session, session-derived
identity only (request-supplied ids ignored), upstream errors forwarded to
next(), and the PDF authz boundary — 404 (never 403) for unknown, not-owned,
and ECLA signatures; presigned URL only for owned ICLAs.
32 tests, all passing.
Ref: specs/001-easycla-ss-integration-fable/m1-my-cla (T013-T015)
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* feat(cla): My CLAs Profile tab UI (EasyCLA M1, T020-T022)
Adds the read-only "My CLAs" Angular view under the Me lens Profile hub,
dark-launched behind the `my-clas-enabled` flag:
- profile.routes.ts: `profile/clas` route gated by a myClasEnabledGuard
(CanMatch); profile-layout appends the "My CLAs" tab (before Transactions)
only when the flag is on, keeping tab + route in lockstep.
- my-clas.service.ts: client for GET /api/me/clas and the ICLA pdf-url endpoint.
- profile-clas.component: ICLA/ECLA sections, loading/error(retry)/empty states,
"history may be incomplete" hint on unmatched identity, GitHub-link CTA into the
identities flow, and a "Sign a new CLA" link-out to the Contributor Console.
PDF download opens a blank tab synchronously (popup-safe) then sets its URL.
- agreement-card.component: per-row kind badge, status label (ICLA), signed date,
and Download PDF for ICLAs / "Covered by Corporate CLA (CCLA)" for ECLAs.
- environments: contributorConsole URL per env (host TBD — see TODO).
- feature-flags: MY_CLAS_ENABLED_FLAG constant.
Also folds in a small test-fixture fix in cla.service.spec.ts (drop a field that
isn't on EasyClaSignature; assert lfUsername instead of a non-existent field) so
the strict-template build is green.
ng build (strict templates) clean apart from a pre-existing unrelated error;
32 server unit tests pass.
Ref: specs/001-easycla-ss-integration-fable/m1-my-cla (T020-T022)
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* fix(cla): use confirmed Contributor Console URLs per environment
EasyCLA team confirmed the sign-out targets:
- dev/local: https://easycla.dev.communitybridge.org/
- staging: https://easycla.staging.communitybridge.org/
- prod: https://contributor.easycla.lfx.linuxfoundation.org/
Removes the placeholder dev/staging hosts and the confirm-with-team TODOs.
Ref: specs/001-easycla-ss-integration-fable/m1-my-cla
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* feat(cla): consume /v4/users/by-identity + projectName (full identity union)
Wires the My CLAs server to the new upstream endpoint (linuxfoundation/easycla#5126):
- resolveIdentity now calls GET /v4/users/by-identity with the three keys
(lfUsername + verified emails + linked GitHub numeric IDs), unioning all
matched EasyCLA user records — replacing the interim username-only lookup.
Falls back to /v3/users/username/{userName} if by-identity 404s, so the
feature still works in environments where it is not yet deployed.
- toMyClaAgreement now prefers the upstream-resolved projectName, falling back
to the CLA Group ID only when the name is absent.
- EasyClaSignature gains an optional projectName field.
Tests updated to the array-returning by-identity shape; added cases for the
union, the query-param wiring, the username-only fallback, and the
projectName-preference. 35 tests pass; tsc + eslint clean.
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* test(cla): extract My CLAs view logic to tested pure helpers (T024)
The repo has no Angular component-test harness (vitest is server/shared only,
no TestBed), so component specs would mean introducing infra no other component
uses. Instead, extract the components' branching logic into framework-free
helpers in @lfx-one/shared and unit-test those:
- cla-view.utils.ts: splitAgreementsByKind, isMyClasEmpty, shouldShowGithubCta,
claKindSeverity, claStatusLabel, claStatusSeverity.
- profile-clas + agreement-card now delegate to these helpers (no behaviour
change; thinner computeds).
- 13 unit tests covering the ICLA/ECLA split, empty-vs-loading-vs-error,
CTA-visibility rules, and status/kind label+severity mapping.
Full suites: 121 server + 366 shared tests pass; ng build clean.
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* fix(cla): keep window handle for PDF download popup (PR #1203 review)
Address review feedback from copilot[bot]:
- profile-clas.component.ts: window.open('', '_blank', 'noopener,noreferrer')
returns null in modern browsers, so the popup-safe download always fell
through to same-tab navigation and could orphan a blank tab. Open without
`noopener` to retain the handle and sever `tab.opener` manually for the same
reverse-tabnabbing protection.
Resolves 1 review thread.
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* style(cla): apply prettier formatting (PR #1203 Code Quality Checks)
Resolves the "Check formatting" failure — prettier --write on the four flagged
files. Formatting-only, no behaviour change.
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* fix(review): address PR #1203 review feedback
Address review comment from cursor[bot]:
- shared/utils/cla-view.utils.ts: add buildProfileTabs(myClasEnabled)
helper that appends the read-only "My CLAs" tab before Transactions
when the flag is on
- sidebar.component.ts: bind the me-lens ⋯ profile menu to
buildProfileTabs() instead of the static PROFILE_TABS, so the sidebar
entry point surfaces My CLAs whenever the flag is on — keeping it in
sync with the profile-layout subtab strip (per cursor[bot])
- profile-layout.component.ts: use the shared buildProfileTabs helper
in place of the inline insert logic (single source of truth)
- cla-view.utils.spec.ts: cover buildProfileTabs (flag on/off, insert
position, no mutation of the shared constant)
Resolves 1 review thread.
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* feat(cla): consume EasyCLA /v4/my-clas endpoints for My CLAs
Switch the read-only "My CLAs" server module from client-side signature
composition (GET /v4/users/by-identity + GET /v4/signatures/user + local
classify/filter/dedupe) to the EasyCLA-composed endpoints merged in
linuxfoundation/easycla#5125 / #5128:
- getMyClas -> single GET /v4/my-clas; upstream computes validity against
the current CCLA approval lists and returns classified/deduped/sorted
ICLAs+ECLAs, mapped 1:1 to the UI view model.
- PDF -> GET /v4/my-clas/{signatureID}/pdf; ownership + ICLA eligibility is
enforced upstream (404 on unknown/not-owned/ECLA), so the SS-side
re-resolve pre-check is removed.
resolveIdentity now sends both githubId and githubUsername: the numeric id
validates upstream against EasyCLA records, the username validates via the
platform user-service, which is the only path that authorizes a pre-LFID
GitHub-only signer whose id has no EasyCLA LFID record.
The linked-identity (GitHub) lookup is best-effort: an auth-service/NATS
failure degrades to LF-username + email resolution instead of failing the
whole page.
Response contract to the frontend is unchanged; the superseded status label
is no longer produced (the endpoint does not expose the current version).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* feat(cla): remove "Sign a new CLA" button from My CLAs header
Drops the Contributor Console link button (and the now-unused
contributorConsoleUrl property + environment import) from the My CLAs
Profile tab header per design.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* fix(clas): correct impersonation token + ICLA PDF-unavailable label (PR #1226)
Address review feedback from copilot[bot], coderabbitai[bot], cursor[bot]:
- cla.service.ts: pass the target user's bearerToken on /v4/my-clas and
/v4/my-clas/{id}/pdf during impersonation, so upstream authorization runs
under the impersonated principal instead of the impersonator's
apiGatewayToken (matches the EnrollmentService pattern). Adds coverage.
- agreement-card.component.html: only show "Covered by Corporate CLA (CCLA)"
for ECLAs; an ICLA with no stored PDF now shows a neutral "PDF unavailable"
instead of a false CCLA-coverage statement.
- clas.controller.ts: correct the trust-model comment — EasyCLA (not SS) is
the ownership authorization boundary; SS only asserts trusted identity keys.
Style/convention and speculative suggestions left as-is (see PR thread replies).
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
* feat(profile): render my clas as a single lfx-table (no status column) (#1269)
* feat(profile): render My CLAs as a single lfx-table (no status column)
Rework the My CLAs profile tab from grouped ICLA/ECLA cards to a single
house lfx-table (Project / Type / Signed / Document) with an always-on
identity-linking info banner, an lfx-empty-state-based error state, and
lfx-table's built-in loading skeleton. Match the approved M1 mockup and
reuse the shared lfx-table instead of a bespoke card/row component.
- Reworked profile-clas.component.html/.ts to render a single lfx-table
- Added an always-on identity-linking info banner (links to /profile/identities)
- Added an lfx-empty-state-based error state and table-shaped loading skeleton
- Deleted the now-unused agreement-card component
- Status column intentionally omitted per the mockup; claStatusLabel/
claStatusSeverity/ClaStatus remain in cla-view.utils.ts as deliberate
deadcode pending design confirmation on #1162
Closes #1162
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
* fix(clas): show only valid CLAs (ICLA + ECLA) in My CLAs list
Filter /v4/my-clas to `valid === true` for both ICLA and ECLA in the BFF
(getMyClas), so every row rendered in the My CLAs table is currently valid.
This makes the approved no-status mockup correct (closes D1) and adds the
previously-missing consumer-side ECLA valid-only display filter (FR-002).
The endpoint intentionally returns invalid rows too (valid=false) and leaves
the final display filter to the consumer; the endpoint owner endorsed skipping
`valid != true` records. Trade-off: invalid/superseded ICLAs are now hidden,
which deviates from #1158's literal "ICLAs in all statuses" wording -- flagged
to design for a courtesy confirm.
Flip the pass-through unit test to assert filtering and revise the component
JSDoc to valid-only.
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
---------
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
* fix(clas): skip My CLAs fetch during SSR to avoid a false error flash
ProfileClasComponent kicked off getMyClas() during SSR with no browser guard.
The server's HTTP call doesn't carry the user's session cookie reliably, so it
tends to fail and bakes a false "Couldn't load your CLAs" error into the SSR
HTML -- a red-banner flash on hydration before the browser's authenticated
fetch resolves.
Guard the fetch with isPlatformBrowser and return a not-loaded state on the
server, mirroring the sibling ProfileIdentitiesComponent and .claude/rules/
ssr-safety.md. The browser re-runs the fetch on hydration.
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
* feat(cla): surface salesforce project name and logo in my clas
The EasyCLA backend now resolves the Salesforce project name and logo for
each signed CLA, so consume the newly-added projectName/projectLogo from
the /v4/my-clas endpoint across the My CLAs stack instead of showing only
the CLA-group name.
- Add projectName/projectLogo to the raw type (cla.types.ts) and the shared
UI interface (cla.interface.ts), renaming the pre-existing projectName
(which was actually the CLA-group name) to claGroupName
- Map the new fields in cla.service.ts, normalizing empty upstream strings
to undefined so template fallbacks trigger correctly
- Render the Salesforce project logo (fa-file-signature fallback) and the
project name (claGroupName as subtext / fallback) in the Project cell of
profile-clas.component.html
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
* refactor(cla): move MyClasState interface to shared
Relocate the MyClasState view-state interface out of the profile-clas
component file into @lfx-one/shared/interfaces (cla.interface.ts), per the
project rule that all interfaces — including component-only view types —
live in the shared barrel rather than local to a component. Switch the
component's remaining type-only imports (MyClaAgreement, MyClasState) to
import type.
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
* feat(cla): send all verified emails for My CLAs (#1227)
## Summary
`ClaService.resolveIdentity` now sends **all** of the user's verified emails to EasyCLA's `GET /v4/my-clas` (previously only the session primary email). Contributors often sign a CLA under a work email that isn't their LFID primary, so those signatures were silently missed. Implements #1227 (follow-up to #1226, part of EPIC #1157).
## What changed
- New exported pure helper `collectClaEmails(primaryEmail, emailData, identities)` — unions three server-side sources, lowercased/trimmed and deduped:
- the session primary email (always included as a floor, so behaviour never regresses);
- the verified-email list from auth-service `user_emails.read` (primary + `verified === true` alternates);
- emails carried on the already-fetched linked identities (`profileData.email`).
- `resolveIdentity` now fetches linked identities and the verified-email set **concurrently** (`Promise.allSettled`), keyed by the effective (impersonation-aware) sub. Both are best-effort: a NATS/auth-service failure degrades to session-primary-email resolution rather than failing the page.
- `identityQuery` already emits each email as a repeated indexed `email=` param, and `getMyClas` already logs `skippedIdentities`, so no change was needed there.
## Why `email`, not `secondaryEmail`
Emails are sent only via the indexed `email` param. EasyCLA's `email` lookup hits an indexed GSI (one query per value); `secondaryEmail` forces a full users-table scan. A work email EasyCLA filed solely as a record's *secondary* email is therefore intentionally not matched — a conscious performance trade-off.
## Safety
Identity keys are still sourced only from the trusted session, never from request input. EasyCLA re-verifies every email against the authenticated user and drops unowned ones into `skippedIdentities`, so sending the full set is safe.
## Testing
- `cd apps/lfx-one && npx vitest run src/server/services/cla.service.spec.ts` — 33 pass (added: `collectClaEmails` helper cases, the multi-email union, and the degraded lookup-returns-null path).
- `npx tsc --noEmit -p tsconfig.app.json` — clean.
- Gauntlet: `./check-headers.sh`, `yarn format:check`, `yarn lint:check`, `yarn check-types` — all pass.
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
* fix(cla): cap My CLAs email set at the upstream 100-item limit (#1227)
collectClaEmails could union more than 100 unique addresses across the
verified-email list and linked identities. /v4/my-clas caps the repeatable
`email` param at maxItems: 100 (easycla cla.v2.yaml), so an oversized set
would fail upstream validation and 400 the whole My CLAs page instead of
degrading. Cap the result at 100; the session primary is added first and
higher-signal sources precede linked-identity emails, so truncation preserves
primary-email priority.
Reviewer feedback: PR #1292, Copilot inline comment on cla.service.ts.
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
---------
Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
What
Two upstream reads that the EasyCLA → LFX Self Serve Milestone-1 "My CLAs" integration needs (Self Serve PR: linuxfoundation/lfx-self-serve#1203). Read-only, no schema changes, no writes.
1.
GET /v4/users/by-identity?lfUsername=…&email=…&githubId=…Resolves the union of EasyCLA user records matching ANY supplied identity key — LF username, verified email(s), or linked GitHub numeric ID(s) — deduped by
user_id, empty array on no match. This lets Self Serve map one logged-in identity to its one-or-more EasyCLA records (including pre-LF-login GitHub-derived history).users.Service(GetUsersByIdentity), reusing existing GSI-backed lookups (lf-username-index,lf-email-index,github-id-index) — no table scans.v2/usershandler is thin request/response translation (v1→v2 model copy), mirroringv2/current_user.cla-servicepublic allow-list (lfx-gateway/dynamic/services/cla-service.yaml), so it lands on the secured router. Self Serve remains the authorization boundary.Known limitation (documented): email matching is against the primary
lf_emailGSI only. A verified email present solely in a user's secondaryuser_emailslist is not resolved — that would require a table scan, which is unsuitable per-request. Username + GitHub ID + primary email cover the common cases.2.
projectNameon the signature responseGET /v4/signatures/user/{userID}now returns each signature's CLA-Group display name (previously only the CLA-Group ID /projectIDwas available). AddedprojectNametoswagger/common/signature.yaml; the v2 handler resolves it from the CLA Group, cached per distinct project ID per request, best-effort (a miss leaves it empty and never fails the listing).Testing
make fmt && go build ./... && go test (users, v2/signatures, cmd) && make lint— all green.gen/is gitignored; regenerate withmake swagger.Notes
GET /v4/signatures/user/{userID}applies here too — treat as an internal/service API; the caller is the authorization boundary.🤖 Generated with Claude Code