feat: grant Semrush workspace members via reused Serenity User Manager transport - #2975
Draft
ravverma wants to merge 7 commits into
Draft
feat: grant Semrush workspace members via reused Serenity User Manager transport#2975ravverma wants to merge 7 commits into
ravverma wants to merge 7 commits into
Conversation
…r Manager transport
SPIKE (throwaway proof, ADR-draft-2): add POST /v2/orgs/:spaceCatId/brands/:brandId/serenity/members
to grant users a Semrush workspace role, reusing the existing Serenity User Manager transport,
IMS-bearer auth, and secrets — no new config.
- rest-transport.js: addWorkspaceMembers(workspaceId, members, role) via the wired `users` client
(POST /v1/workspaces/{id}/members; body { members, role }). Present in user-manager-client 1.5.0.
- controllers/serenity.js: addMembers handler (resolveSemrushImsToken -> authorize ->
resolveBrandWorkspace -> transport); validates members[], role defaults role/workspace/viewer.
- routes/index.js + facs-capabilities.js (llmo/can_configure) + route/controller unit + route tests.
Caller's IMS token is forwarded as-is (Semrush is the auth boundary). Open: correct capability level
and whether non-@semrush.com emails require IMS->Semrush identity mapping — verify in dev.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
This PR will trigger a minor release when merged. |
The route-coverage test requires every route in routes/index.js to appear in routeRequiredCapabilities or INTERNAL_ROUTES. Classify the new members grant as organization:write, matching sibling serenity write ops (activate/deactivate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ct type-check The new type-check:strict tier (noImplicitAny) flagged the spike transport method's untyped params. Annotate workspaceId/members/role per the serenity dir's JSDoc rule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…ad of caller token The /serenity/members grant is now authenticated by a token minted for a dedicated Semrush IMS technical account (SEMRUSH_IMS_TECH_ID/SECRET, client_credentials) rather than the calling user's IMS token. This lets the "user hits 401/403 → provision them" flow add a user who is not yet a member of the workspace: the mint identity holds the member-management rights, the end user need not. - add src/support/serenity/semrush-ims-token.js: mintSemrushImsToken(env, log) — client_credentials mint; token endpoint host from IMS_HOST; scope override via SEMRUSH_IMS_TECH_SCOPE; 503 on missing config, 502 on IMS failure; never logs secrets. - controllers/serenity.js addMembers: org-level authorize + validation first, THEN mint the dedicated token, THEN call the transport (no more caller/promise-token path here). - rest-transport addWorkspaceMembers doc updated (bearer = minted dedicated IMS token). - tests: mint unit tests + addMembers uses-minted-token / mint-after-gate / mint-config failure; .env.example documents the new vars. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-gated) Productizes the member-add mechanism as the real flow: when a brand-scoped Semrush data read fails because the caller is not yet a workspace member (401/403), provision them (viewer) using the dedicated Semrush IMS token and retry the read once. - add src/support/serenity/member-autoprovision.js: withMemberAutoProvision (single retry, best-effort — a failed grant surfaces the original read error; no-op when disabled / missing ids / non-401-403) + isSemrushMembershipDenied. - controllers/serenity.js: wrap the 5 brand-scoped reads (prompts, markets, getMarket, tags, models) via readWithProvision; gate on SERENITY_MEMBER_AUTOPROVISION (default off) + the existing serenity-active/workspace gate; resolve caller email. - mapError: surface upstream 422 as unprocessableEntity (was flattened to 502); the limit/emails body stays server-side only. - utils.js: resolveCallerEmail. .env.example documents the flag. - tests: member-autoprovision (9), addMembers 422 passthrough, read-provision wiring (3). The standalone POST /serenity/members endpoint is kept for testing and must be removed before merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Adds the ability to grant a user a Semrush workspace role, reusing the existing Serenity (Semrush) User Manager integration. This is the mechanism behind the intended flow: user's Semrush data fetch fails 403 (not yet a member) → provision them onto the workspace → retry.
Endpoint (proving ground for the mechanism):
Resolves the brand's Semrush workspace (
resolveBrandWorkspace— sub-workspace or flat parent) and calls Semrush User ManagerPOST /v1/workspaces/{id}/members.Auth — dedicated Semrush IMS technical account (not the caller's token)
The outbound Semrush call is authenticated with a token minted for a dedicated Semrush IMS technical account (
SEMRUSH_IMS_TECH_ID/SEMRUSH_IMS_TECH_SECRET,client_credentials) — not the calling user's token, and not the app's existing IMS service client.This is deliberate: the provisioning flow must be able to add a user who is not yet a member of the workspace. The mint identity holds the member-management rights, so the end user doesn't need them. The Adobe caller is still authorized at the org level (
AccessControlUtil) before anything is minted.src/support/serenity/semrush-ims-token.js→mintSemrushImsToken(env, log):client_credentialsmint; token host fromIMS_HOST; scope defaults toopenid,AdobeID,user_management_sdk,additional_info.projectedProductContext(override viaSEMRUSH_IMS_TECH_SCOPE);503on missing config,502on IMS failure; never logs secrets/token.addMembersorder: authorize (org) → validatemembers[]→ mint token → transport, so a bad/unauthorized request never mints.Changes
semrush-ims-token.js(new) +addMemberscontroller +addWorkspaceMemberstransport.routes/index.js,facs-capabilities.js(llmo/can_configure),required-capabilities.js(organization:write).addMembers(10, incl. "uses minted token not caller", mint ordering, mint-config503)..env.exampledocuments the new vars.Open (verify before productizing)
@semrush.comtarget-eligibility — the User Manager spec notes external (non-@semrush.com) users can't be invited; whether anadobe.commember resolves depends on Semrush-side identity mapping. Needs a dev/live check.401(identity invalid; not fixable by add-member).Verification
type-check(base+strict) ✓ ·lint✓ · serenity controller + mint suites (239) ✓. Not yet exercised against live/dev Semrush.🤖 Generated with Claude Code