Skip to content

Update profile data edit API endpoints to use PUT /users/me#9

Open
janithjay wants to merge 1 commit into
thunder-id:mainfrom
janithjay:feat/profile-edit-put
Open

Update profile data edit API endpoints to use PUT /users/me#9
janithjay wants to merge 1 commit into
thunder-id:mainfrom
janithjay:feat/profile-edit-put

Conversation

@janithjay

@janithjay janithjay commented Jun 26, 2026

Copy link
Copy Markdown

Purpose

Currently, the <UserProfile />component attempts to save changes by making a PATCH request using the SCIM2 format to a non-existent /scim2/Me endpoint. This prevents self-service profile updates from persisting.

This PR resolves this limitation by migrating the self-service profile read and update path on the React & Javascript SDKs from SCIM-based PATCH (/scim2/Me) to standard JSON-based PUT (/users/me), aligning with the existing endpoint supported by the backend (GET & PUT on /users/me). This enables profile updates to work end-to-end.

Approach

Endpoint & Method Realignment

  • Updated the base URL in updateMeProfile.ts (JS SDK) and getScim2Me.ts (JS SDK) to target ${baseUrl}/users/me instead of ${baseUrl}/scim2/Me.
  • Updated the HTTP method for profile updates to PUT and request headers to Content-Type: application/json.
  • Updated updateMeProfile.ts (React SDK) HTTP wrapper configuration to default to PUT.

Payload Structure Formatting

  • Wrapped the attributes inside an attributes wrapper object (e.g. { attributes: payload }) to match the backend UpdateSelfUserRequest payload contract.
  • Stripped out any nullor undefined properties from the attributes payload inside UserProfile.tsx before dispatching the request.

Response Handling

  • Backend returns attributes nested under the attributes field of the User object. Flattened these nested attributes back to the top-level User object inside updateMeProfile.ts and getScim2Me.ts to preserve backward compatibility for downstream React UI component attributes resolution.

React State Syncing

Added an useEffect hook in BaseUserProfile.tsx to listen to updates on profile or flattenedProfile and automatically reset/sync the local input states, resolving any stale UI rendering after a successful profile update.

Test Assertions:

Updated the unit tests inside updateMeProfile.test.ts and getScim2Me.test.ts to validate the new PUT/GET behavior, URL paths, content-type headers, and payload attributes structure.

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features

    • Profile data is now loaded and updated through the standard /users/me endpoint.
    • User profile updates now support editable fields without relying on schema data.
    • Theme CSS variables now use the --thunderid-* naming pattern.
  • Bug Fixes

    • Request headers now consistently use JSON for profile operations.
    • Authorization requests no longer include an empty scope when none is provided.
    • Profile data merging preserves existing values while applying updates.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR migrates profile read/write operations from SCIM2 endpoints (/scim2/Me, /scim2/Schemas) to /users/me across javascript, react, nextjs, nuxt, and vue packages. getScim2Me/getSchemas are replaced with getUsersMe; updateMeProfile switches from SCIM PATCH to JSON PUT with an attributes wrapper. Schema types and props are removed throughout, and error codes, docs, and tests are updated accordingly.

Changes

Users me profile migration (core + React)

Layer / File(s) Summary
Read profile from /users/me
packages/javascript/src/api/getUsersMe.ts, packages/javascript/src/api/__tests__/getUsersMe.test.ts, packages/javascript/src/index.ts, packages/javascript/src/models/user.ts, packages/javascript/src/models/config.ts, packages/javascript/src/utils/processUsername.ts, packages/javascript/src/constants/ScopeConstants.ts, packages/javascript/src/utils/generateFlattenedUserProfile.ts
getUsersMe (renamed from getScim2Me) defaults to /users/me, uses JSON headers, merges attributes into the returned user, updates error codes/exports, and generateFlattenedUserProfile is simplified to a shallow copy without schema processing.
PUT profile updates via /users/me
packages/javascript/src/api/updateMeProfile.ts, packages/javascript/src/api/__tests__/updateMeProfile.test.ts, packages/react/src/api/updateMeProfile.ts, packages/react/src/api/getUsersMe.ts, packages/react/src/index.ts
updateMeProfile sends { attributes: payload } via PUT to /users/me, forces JSON headers, and merges response attributes; React wraps getUsersMe/updateMeProfile and re-exports accordingly.
React profile editor
packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx, packages/react/src/components/presentation/UserProfile/UserProfile.tsx
BaseUserProfile drops the schemas prop, resyncs edited fields via useEffect, and simplifies save logic; UserProfile deep-merges attributes and gates edit ability before calling updateMeProfile.
Session hydration and user context
packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx, packages/react/src/contexts/ThunderID/ThunderIDContext.ts, packages/react/src/contexts/User/UserContext.ts, packages/react/src/contexts/User/UserProvider.tsx, packages/react/src/contexts/User/useUser.ts
ThunderIDProvider fetches /users/me during session updates, exposes preferences, and UserContext/UserProvider drop schemas from typing and defaults.

Cross-framework users-me adoption and schema removal

Layer / File(s) Summary
Next.js client and provider
packages/nextjs/src/ThunderIDNextClient.ts, packages/nextjs/src/client/components/presentation/UserProfile/UserProfile.tsx, packages/nextjs/src/client/contexts/ThunderID/ThunderIDProvider.tsx, packages/nextjs/src/server/ThunderIDProvider.tsx, packages/nextjs/src/server/actions/getUserProfileAction.ts
Switches to getUsersMe/generateFlattenedUserProfile, removes schemas from props/state, and updates logging.
Nuxt runtime and SSR
packages/nuxt/src/runtime/components/ThunderIDRoot.ts, packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts, packages/nuxt/src/runtime/errors/error-codes.ts, packages/nuxt/src/runtime/server/plugins/thunderid-ssr.ts, packages/nuxt/src/runtime/server/routes/auth/user/profile.*, packages/nuxt/src/runtime/types.ts, packages/nuxt/tests/unit/*
Removes schemas from profile state/props, renames scim2/* error codes to user-profile/*, and updates docs/tests accordingly.
Vue client, components, and providers
packages/vue/src/ThunderIDVueClient.ts, packages/vue/src/api/getUsersMe.ts, packages/vue/src/components/presentation/user-profile/*, packages/vue/src/models/contexts.ts, packages/vue/src/providers/*
Switches to getUsersMe/generateFlattenedUserProfile, replaces buildScimPatchValue with buildPatchValue, and removes schemas from types/props/context.

Auth request params and callback tests

Layer / File(s) Summary
Authorize scope parameter
packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts
scope is added only when options.scopes is explicitly provided.
Callback test cleanup and context wiring
packages/react/src/components/auth/Callback/__tests__/Callback.test.tsx, packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx, packages/react/package.json
Tests call cleanup() after each case; TokenCallback tests render via ThunderIDContext.Provider instead of mocking useThunderID; adds @vitest/browser-playwright dev dependency.

Shared regression expectations

Layer / File(s) Summary
Structured error and theme test fixtures
packages/javascript/src/errors/__tests__/ThunderIDAPIError.test.ts, packages/javascript/src/theme/createTheme.test.ts
Structured error fixture drops message.defaultValue; theme tests expect --thunderid-* CSS variables instead of --thunder-*.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UI as UserProfile
  participant updateMeProfile
  participant UsersMeAPI as "/users/me"
  UI->>updateMeProfile: PUT with { attributes: payload }
  updateMeProfile->>UsersMeAPI: send JSON PUT request
  UsersMeAPI-->>updateMeProfile: updated user with attributes
  updateMeProfile->>updateMeProfile: merge attributes into user
  updateMeProfile-->>UI: processed User
Loading
sequenceDiagram
  participant App
  participant ThunderIDProvider
  participant Client
  participant UsersMeAPI as "/users/me"
  App->>ThunderIDProvider: updateSession()
  ThunderIDProvider->>Client: isSignedIn()
  Client-->>ThunderIDProvider: signed-in status
  alt signed in and fetchUserProfile enabled
    ThunderIDProvider->>UsersMeAPI: getUsersMe(baseUrl, instanceId)
    UsersMeAPI-->>ThunderIDProvider: profile data
    ThunderIDProvider->>ThunderIDProvider: merge profile into claims
  end
  ThunderIDProvider-->>App: user, userProfile, flattenedProfile
Loading

Possibly related issues

Suggested reviewers: JayaShakthi97

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly states the main change: switching profile edit APIs to PUT /users/me.
Description check ✅ Passed The description follows the template and covers purpose, approach, related items, checklist, and security notes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx (1)

23-47: 📐 Maintainability & Code Quality | 🟡 Minor

Type thunderIDContext as ThunderIDContext

The thunderIDContext fixture is currently declared with : any. This forces TypeScript to treat the entire context object as unsafe, triggering @typescript-eslint/no-unsafe-assignment errors when its properties are accessed in the test assertions and render calls.

Change the declaration to const thunderIDContext: ThunderIDContext = ... (or a compatible shape) to satisfy the type checker and ensure the tests validate the actual context contract.

🤖 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 `@packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx`
around lines 23 - 47, The TokenCallback test fixture is using an unsafe
any-typed context object, which causes type-checking issues in the assertions
and render setup. Update the thunderIDContext declaration to use
ThunderIDContext (or a compatible typed shape) and keep the existing mocked
methods like getStorageManager, signIn, and signUp typed accordingly so the test
exercises the real context contract without unsafe assignments.
🤖 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 `@packages/javascript/src/api/__tests__/getScim2Me.test.ts`:
- Around line 24-37: The test fixture for getScim2Me uses a broad Record<string,
unknown> shape, which leaves mockUserResponse.attributes typed as unknown and
triggers the unsafe spread in mockUser. Update the mock definitions in
getScim2Me.test.ts so mockUserResponse has an explicit attributes type (or use
satisfies with a precise interface), and keep mockUser built from that strongly
typed attributes object to satisfy the lint rule.

In `@packages/javascript/src/api/__tests__/updateMeProfile.test.ts`:
- Around line 54-62: The test assertions are reading mocked fetch calls through
an untyped/any path, which is triggering ESLint. In the updateMeProfile test
cases, cast the first entry from Mock.mock.calls to a typed tuple like [string,
RequestInit] before destructuring, and use the typed RequestInit/body access for
JSON.parse instead of going through any. Apply the same cleanup in the other
affected assertions as well, using the existing fetch mock and RequestInit
references to keep the types explicit.

In `@packages/javascript/src/api/getScim2Me.ts`:
- Around line 137-142: The normalization block in getScim2Me currently spreads
user['attributes'] directly, which lets the index signature collapse
processedUser and the return value to any. Narrow or cast attributes to a
concrete object type before flattening inside getScim2Me, then build
processedUser from that typed value and pass the typed result into
processUserUsername so the new path stays lint-clean.

In `@packages/javascript/src/api/updateMeProfile.ts`:
- Around line 144-149: The update path in updateMeProfile currently spreads
user['attributes'] directly, which leaves the merged result unsafe and triggers
no-unsafe-assignment/no-unsafe-return. Narrow or type attributes as an object
before constructing processedUser, then return the typed flattened user through
processUserUsername so the helper stays type-safe like the read path.
- Around line 113-116: The request setup in updateMeProfile should not allow
callers to override the new PUT contract. In the requestInit object used by
updateMeProfile, ensure the spread of requestConfig happens before the method
assignment, or explicitly reassign method after spreading, so a caller cannot
pass a different HTTP verb and bypass the /users/me PUT migration.

In `@packages/react/package.json`:
- Around line 54-58: The devDependencies block in package.json has a duplicated
`@vitest/browser-playwright` entry, which creates an invalid/ambiguous manifest.
Remove one of the duplicate entries from the package manifest and keep only a
single `@vitest/browser-playwright` key alongside the other devDependencies.

In `@packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx`:
- Around line 145-147: The useEffect in BaseUserProfile is overwriting the
entire editedUser state whenever flattenedProfile or profile changes, which
clobbers other in-flight edits tracked by editingFields. Update this sync logic
so it only merges in refreshed values for fields that are not currently being
edited, or skip resetting editedUser while any field is dirty. Keep the fix
localized to the BaseUserProfile state synchronization path around setEditedUser
and editingFields.

In `@packages/react/src/components/presentation/UserProfile/UserProfile.tsx`:
- Around line 78-87: The `updatedAttributes` merge in `UserProfile` is shallow,
so nested patches from `BaseUserProfile` (for example `name.givenName`)
overwrite sibling fields in `attributes`. Update the merge logic to deep-merge
`payload` into the existing `profile['attributes']` document instead of using a
top-level spread, and keep the existing cleanup that removes `undefined`/`null`
values before the `PUT`.

In `@packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx`:
- Around line 21-29: The ThunderIDProvider import list includes an unused
HttpResponse symbol, which keeps the lint error active. Remove HttpResponse from
the imports in ThunderIDProvider so the remaining symbols like
ThunderIDRuntimeError, generateFlattenedUserProfile, and UserProfile are the
only referenced imports.
- Around line 59-109: The hardcoded DEFAULT_USER_PROFILE_SCHEMAS in
ThunderIDProvider is causing BaseUserProfile to render only the six built-in
fields and hide any custom or extension profile attributes. Update the schemas
flow so it comes from the authoritative profile metadata when available, or keep
schemas empty when using the flattened object from
generateFlattenedUserProfile(), ensuring BaseUserProfile can still surface all
non-default fields.
- Around line 214-227: In ThunderIDProvider’s user-profile load flow, the
/users/me failure path is leaving profileData populated from claims only, which
makes the profile editable without a full attributes snapshot. Update the logic
around getScim2Me and the subsequent setUser/setUserProfile so that, when the
fetch fails, you either keep the previously fetched profile.attributes state or
mark the profile as read-only instead of falling back to a claims-only editable
profile. Make sure the downstream PUT /users/me merge path still has a complete
attributes object, using the existing profileData, generateFlattenedUserProfile,
and DEFAULT_USER_PROFILE_SCHEMAS flow as the key points to adjust.

---

Outside diff comments:
In
`@packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx`:
- Around line 23-47: The TokenCallback test fixture is using an unsafe any-typed
context object, which causes type-checking issues in the assertions and render
setup. Update the thunderIDContext declaration to use ThunderIDContext (or a
compatible typed shape) and keep the existing mocked methods like
getStorageManager, signIn, and signUp typed accordingly so the test exercises
the real context contract without unsafe assignments.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cce87ffa-d726-4f5b-b942-01afbf49a26f

📥 Commits

Reviewing files that changed from the base of the PR and between a4f4283 and 3071aa8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • packages/browser/package.json
  • packages/javascript/package.json
  • packages/javascript/src/api/__tests__/getScim2Me.test.ts
  • packages/javascript/src/api/__tests__/updateMeProfile.test.ts
  • packages/javascript/src/api/getScim2Me.ts
  • packages/javascript/src/api/updateMeProfile.ts
  • packages/javascript/src/errors/__tests__/ThunderIDAPIError.test.ts
  • packages/javascript/src/theme/createTheme.test.ts
  • packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts
  • packages/react-router/package.json
  • packages/react/package.json
  • packages/react/src/api/updateMeProfile.ts
  • packages/react/src/components/auth/Callback/__tests__/Callback.test.tsx
  • packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx
  • packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx
  • packages/react/src/components/presentation/UserProfile/UserProfile.tsx
  • packages/react/src/contexts/ThunderID/ThunderIDContext.ts
  • packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx
💤 Files with no reviewable changes (1)
  • packages/javascript/src/errors/tests/ThunderIDAPIError.test.ts

Comment thread packages/javascript/src/api/__tests__/getUsersMe.test.ts
Comment thread packages/javascript/src/api/__tests__/updateMeProfile.test.ts
Comment thread packages/javascript/src/api/getScim2Me.ts Outdated
Comment thread packages/javascript/src/api/updateMeProfile.ts
Comment thread packages/javascript/src/api/updateMeProfile.ts
Comment thread packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx Outdated
Comment thread packages/react/src/components/presentation/UserProfile/UserProfile.tsx Outdated
Comment thread packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx Outdated
Comment thread packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx Outdated
Comment thread packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx Outdated
@janithjay janithjay force-pushed the feat/profile-edit-put branch from 3071aa8 to 50bf8a9 Compare June 26, 2026 09:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx`:
- Around line 59-107: The default user profile schema in ThunderIDProvider is
still using snake_case keys, which no longer match the /users/me contract.
Update DEFAULT_USER_PROFILE_SCHEMAS to use the camelCase attribute names
expected by the read/write flow and tests, and make sure the corresponding
schema entries in ThunderIDProvider stay consistent with the values used when
hydrating and saving the profile.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ed55479-ef81-47dd-945d-14055369fa96

📥 Commits

Reviewing files that changed from the base of the PR and between 3071aa8 and 50bf8a9.

📒 Files selected for processing (14)
  • packages/javascript/src/api/__tests__/getScim2Me.test.ts
  • packages/javascript/src/api/__tests__/updateMeProfile.test.ts
  • packages/javascript/src/api/getScim2Me.ts
  • packages/javascript/src/api/updateMeProfile.ts
  • packages/javascript/src/errors/__tests__/ThunderIDAPIError.test.ts
  • packages/javascript/src/theme/createTheme.test.ts
  • packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts
  • packages/react/src/api/updateMeProfile.ts
  • packages/react/src/components/auth/Callback/__tests__/Callback.test.tsx
  • packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx
  • packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx
  • packages/react/src/components/presentation/UserProfile/UserProfile.tsx
  • packages/react/src/contexts/ThunderID/ThunderIDContext.ts
  • packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx
💤 Files with no reviewable changes (1)
  • packages/javascript/src/errors/tests/ThunderIDAPIError.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/react/src/contexts/ThunderID/ThunderIDContext.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/react/src/components/auth/Callback/tests/Callback.test.tsx
  • packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts
  • packages/javascript/src/theme/createTheme.test.ts
  • packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx

Comment thread packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx Outdated
@janithjay janithjay force-pushed the feat/profile-edit-put branch from 50bf8a9 to fbaff18 Compare June 26, 2026 09:53
@janithjay janithjay marked this pull request as draft June 30, 2026 04:34
@janithjay janithjay force-pushed the feat/profile-edit-put branch from fbaff18 to 1f7e1f5 Compare June 30, 2026 08:17
@janithjay janithjay marked this pull request as ready for review June 30, 2026 08:26
@JayaShakthi97 JayaShakthi97 changed the title Update profile data edit API endpoints to /users/me and switch from PATCH to PUT (React package) [react] Update profile data edit API endpoints to use PUT /users/me Jun 30, 2026
@janithjay janithjay marked this pull request as draft June 30, 2026 11:11
@janithjay janithjay force-pushed the feat/profile-edit-put branch 2 times, most recently from 0bfd8b3 to 8199b64 Compare June 30, 2026 13:29
@janithjay janithjay marked this pull request as ready for review June 30, 2026 16:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@packages/javascript/src/api/updateMeProfile.ts`:
- Around line 106-108: The updateMeProfile helper is still wrapping any runtime
payload directly into attributes, so non-object values can slip through. Add a
guard in updateMeProfile before building the data object to reject null, arrays,
and primitives, and only proceed when payload is a plain object. Use the
updateMeProfile payload handling around the attributes assignment to locate the
change and make the validation happen before the request body is constructed.
- Around line 113-120: The updateMeProfile request setup is merging
requestConfig.headers with object spread, which loses values when headers is a
Headers instance or header tuples. In updateMeProfile, normalize
requestConfig.headers with new Headers(...) before adding Accept and
Content-Type so existing auth/routing headers are preserved in the RequestInit
headers.

In
`@packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx`:
- Around line 73-77: The shared test fixtures in TokenCallback.test.tsx are
still typed as any, which keeps the unsafe-assignment warnings around the render
setup. Update the reusable ThunderIDContext fixture and the onNavigate/onError
callback mocks to use real types inferred from TokenCallback and
ThunderIDContext, then reuse those typed fixtures across all test cases so the
render calls no longer rely on any.

In `@packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx`:
- Around line 145-159: The sync logic in BaseUserProfile’s useEffect and
setEditedUser currently starts from prev, so keys that disappear from nextUser
remain in editedUser. Update the merge in this effect to rebuild the edited
state from the refreshed profile and remove any non-editing fields that are no
longer present in nextUser, while still preserving fields currently being edited
via editingFields.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bf8c426c-d3cc-4435-b789-632dd98ac63f

📥 Commits

Reviewing files that changed from the base of the PR and between 1f7e1f5 and 8199b64.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • packages/javascript/src/api/__tests__/getScim2Me.test.ts
  • packages/javascript/src/api/__tests__/updateMeProfile.test.ts
  • packages/javascript/src/api/getScim2Me.ts
  • packages/javascript/src/api/updateMeProfile.ts
  • packages/javascript/src/errors/__tests__/ThunderIDAPIError.test.ts
  • packages/javascript/src/models/config.ts
  • packages/javascript/src/theme/createTheme.test.ts
  • packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts
  • packages/react/package.json
  • packages/react/src/api/updateMeProfile.ts
  • packages/react/src/components/auth/Callback/__tests__/Callback.test.tsx
  • packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx
  • packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx
  • packages/react/src/components/presentation/UserProfile/UserProfile.tsx
  • packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx
💤 Files with no reviewable changes (1)
  • packages/javascript/src/errors/tests/ThunderIDAPIError.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/react/src/components/presentation/UserProfile/UserProfile.tsx
  • packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts
  • packages/javascript/src/api/getScim2Me.ts
  • packages/react/src/components/auth/Callback/tests/Callback.test.tsx
  • packages/javascript/src/theme/createTheme.test.ts
  • packages/react/package.json
  • packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx

Comment thread packages/javascript/src/api/updateMeProfile.ts
Comment thread packages/javascript/src/api/updateMeProfile.ts
Comment thread packages/javascript/src/api/getUsersMe.ts
Comment thread packages/javascript/src/models/config.ts Outdated
@janithjay janithjay marked this pull request as draft July 2, 2026 04:04
@janithjay janithjay force-pushed the feat/profile-edit-put branch 3 times, most recently from 12a27f2 to 8ad53f9 Compare July 3, 2026 12:42
@janithjay janithjay marked this pull request as ready for review July 3, 2026 12:48
Copilot AI review requested due to automatic review settings July 3, 2026 12:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/vue/src/ThunderIDVueClient.ts (1)

98-113: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass instanceId to getUsersMe
getUsersMe defaults to instanceId = 0, so non-zero Vue clients will read through the wrong HTTP client here. Match getUserProfile() and forward this.getInstanceId().

🐛 Proposed fix
-      const profile: User = await getUsersMe({baseUrl});
+      const profile: User = await getUsersMe({baseUrl, instanceId: this.getInstanceId()});
🤖 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 `@packages/vue/src/ThunderIDVueClient.ts` around lines 98 - 113, The getUser
flow in ThunderIDVueClient currently calls getUsersMe with only baseUrl, so it
falls back to instanceId = 0 and can use the wrong HTTP client. Update the
getUser method to forward this.getInstanceId() to getUsersMe, matching the
behavior used by getUserProfile and preserving the correct client context for
non-zero Vue instances.
🧹 Nitpick comments (3)
packages/vue/src/ThunderIDVueClient.ts (1)

141-146: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Avoid decoding the ID token twice in the error fallback.

getDecodedIdToken() is awaited twice to build identical claims. Decode once and reuse.

♻️ Proposed refactor
       } catch (error) {
+        const claims: User = extractUserClaimsFromIdToken(await this.getDecodedIdToken());
         return {
-          flattenedProfile: extractUserClaimsFromIdToken(await this.getDecodedIdToken()),
-          profile: extractUserClaimsFromIdToken(await this.getDecodedIdToken()),
+          flattenedProfile: claims,
+          profile: claims,
         };
       }
🤖 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 `@packages/vue/src/ThunderIDVueClient.ts` around lines 141 - 146, The error
fallback in ThunderIDVueClient.getProfile is decoding the ID token twice by
calling getDecodedIdToken() separately for flattenedProfile and profile. Update
the catch block to await getDecodedIdToken() once, store the decoded token in a
local variable, and reuse that value for both extractUserClaimsFromIdToken calls
so the fallback builds identical claims from a single decode.
packages/react/src/api/getUsersMe.ts (1)

80-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop unnecessary async and prefer ??.

The arrow function is marked async but never uses await — it just returns the promise from baseGetUsersMe, which triggers the require-await lint error. Also prefer ?? over || for fetcher.

🧹 Proposed fix
-const getUsersMe = async ({fetcher, instanceId = 0, ...requestConfig}: GetUsersMeConfig): Promise<User> => {
+const getUsersMe = ({fetcher, instanceId = 0, ...requestConfig}: GetUsersMeConfig): Promise<User> => {
   const defaultFetcher = async (url: string, config: RequestInit): Promise<Response> => {
     ...
   };

   return baseGetUsersMe({
     ...requestConfig,
-    fetcher: fetcher || defaultFetcher,
+    fetcher: fetcher ?? defaultFetcher,
   });
 };
🤖 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 `@packages/react/src/api/getUsersMe.ts` around lines 80 - 102, The getUsersMe
helper is using an unnecessary async wrapper and a fallback check that should
preserve valid falsy values. Update getUsersMe and its defaultFetcher so the
outer function is no longer marked async if it only returns baseGetUsersMe, and
replace fetcher || defaultFetcher with fetcher ?? defaultFetcher. Keep the
FetchHttpClient.getInstance and baseGetUsersMe wiring intact while adjusting
only the async handling and nullish fallback.

Source: Linters/SAST tools

packages/javascript/src/api/getUsersMe.ts (1)

93-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tighten catch typing and prefer ??.

Static analysis flags error?.toString() as an unsafe call/member access on an implicitly-any catch binding, and fetcher || fetch should use ?? since fetcher is a function reference (never a legitimate falsy-but-defined value).

🧹 Proposed fix
-  } catch (error) {
+  } catch (error: unknown) {
     throw new ThunderIDAPIError(
-      `Invalid URL provided. ${error?.toString()}`,
+      `Invalid URL provided. ${error instanceof Error ? error.message : String(error)}`,
       'getUsersMe-ValidationError-001',
...
-  const fetchFn: typeof fetch = fetcher || fetch;
+  const fetchFn: typeof fetch = fetcher ?? fetch;
🤖 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 `@packages/javascript/src/api/getUsersMe.ts` around lines 93 - 107, The
`getUsersMe` validation catch block uses an implicitly-typed `error` and unsafe
`error?.toString()`, so update the `try/catch` in `getUsersMe` to type the catch
binding properly and safely derive the message before constructing
`ThunderIDAPIError`. Also change the `fetchFn` assignment in `getUsersMe` to use
`fetcher ?? fetch` instead of `fetcher || fetch`, since `fetcher` is a function
reference and should only fall back when it is nullish.

Source: Linters/SAST tools

🤖 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 `@packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx`:
- Around line 660-669: The profile entry rendering in BaseUserProfile is forcing
every synthesized field schema to READ_WRITE, which makes immutable attributes
appear editable. Update the profileEntries map logic to preserve each field’s
original mutability from the source schema (or explicitly mark non-editable
attributes as read-only) before passing schemaWithValue to renderUserInfo, and
align the readonlyFields/fieldsToSkip handling so IDs, timestamps, and other
immutable fields are not exposed as editable.

In `@packages/react/src/components/presentation/UserProfile/UserProfile.tsx`:
- Line 24: The UserProfile component has an unused getUsersMe import that
triggers lint failures; remove the getUsersMe import from UserProfile.tsx and
keep the rest of the component unchanged. Use the UserProfile component and the
getUsersMe import statement as the symbols to locate the unused reference.

---

Outside diff comments:
In `@packages/vue/src/ThunderIDVueClient.ts`:
- Around line 98-113: The getUser flow in ThunderIDVueClient currently calls
getUsersMe with only baseUrl, so it falls back to instanceId = 0 and can use the
wrong HTTP client. Update the getUser method to forward this.getInstanceId() to
getUsersMe, matching the behavior used by getUserProfile and preserving the
correct client context for non-zero Vue instances.

---

Nitpick comments:
In `@packages/javascript/src/api/getUsersMe.ts`:
- Around line 93-107: The `getUsersMe` validation catch block uses an
implicitly-typed `error` and unsafe `error?.toString()`, so update the
`try/catch` in `getUsersMe` to type the catch binding properly and safely derive
the message before constructing `ThunderIDAPIError`. Also change the `fetchFn`
assignment in `getUsersMe` to use `fetcher ?? fetch` instead of `fetcher ||
fetch`, since `fetcher` is a function reference and should only fall back when
it is nullish.

In `@packages/react/src/api/getUsersMe.ts`:
- Around line 80-102: The getUsersMe helper is using an unnecessary async
wrapper and a fallback check that should preserve valid falsy values. Update
getUsersMe and its defaultFetcher so the outer function is no longer marked
async if it only returns baseGetUsersMe, and replace fetcher || defaultFetcher
with fetcher ?? defaultFetcher. Keep the FetchHttpClient.getInstance and
baseGetUsersMe wiring intact while adjusting only the async handling and nullish
fallback.

In `@packages/vue/src/ThunderIDVueClient.ts`:
- Around line 141-146: The error fallback in ThunderIDVueClient.getProfile is
decoding the ID token twice by calling getDecodedIdToken() separately for
flattenedProfile and profile. Update the catch block to await
getDecodedIdToken() once, store the decoded token in a local variable, and reuse
that value for both extractUserClaimsFromIdToken calls so the fallback builds
identical claims from a single decode.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 86bfd088-6dc1-4fe9-a1b9-78029701d208

📥 Commits

Reviewing files that changed from the base of the PR and between 8199b64 and 8ad53f9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (55)
  • packages/javascript/src/api/__tests__/getSchemas.test.ts
  • packages/javascript/src/api/__tests__/getUsersMe.test.ts
  • packages/javascript/src/api/__tests__/updateMeProfile.test.ts
  • packages/javascript/src/api/getSchemas.ts
  • packages/javascript/src/api/getUsersMe.ts
  • packages/javascript/src/api/updateMeProfile.ts
  • packages/javascript/src/constants/ScopeConstants.ts
  • packages/javascript/src/errors/__tests__/ThunderIDAPIError.test.ts
  • packages/javascript/src/index.ts
  • packages/javascript/src/models/config.ts
  • packages/javascript/src/models/scim2-schema.ts
  • packages/javascript/src/models/user.ts
  • packages/javascript/src/theme/createTheme.test.ts
  • packages/javascript/src/utils/__tests__/flattenUserSchema.test.ts
  • packages/javascript/src/utils/__tests__/generateUserProfile.test.ts
  • packages/javascript/src/utils/flattenUserSchema.ts
  • packages/javascript/src/utils/generateFlattenedUserProfile.ts
  • packages/javascript/src/utils/generateUserProfile.ts
  • packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts
  • packages/javascript/src/utils/processUsername.ts
  • packages/nextjs/src/ThunderIDNextClient.ts
  • packages/nextjs/src/client/components/presentation/UserProfile/UserProfile.tsx
  • packages/nextjs/src/client/contexts/ThunderID/ThunderIDProvider.tsx
  • packages/nextjs/src/server/ThunderIDProvider.tsx
  • packages/nextjs/src/server/actions/getUserProfileAction.ts
  • packages/nuxt/src/runtime/components/ThunderIDRoot.ts
  • packages/nuxt/src/runtime/errors/error-codes.ts
  • packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts
  • packages/nuxt/src/runtime/server/plugins/thunderid-ssr.ts
  • packages/nuxt/src/runtime/server/routes/auth/user/profile.get.ts
  • packages/nuxt/src/runtime/server/routes/auth/user/profile.patch.ts
  • packages/nuxt/src/runtime/types.ts
  • packages/nuxt/tests/unit/thunderid-root.test.ts
  • packages/nuxt/tests/unit/thunderid-ssr.test.ts
  • packages/react/package.json
  • packages/react/src/api/getUsersMe.ts
  • packages/react/src/api/updateMeProfile.ts
  • packages/react/src/components/auth/Callback/__tests__/Callback.test.tsx
  • packages/react/src/components/auth/Callback/__tests__/TokenCallback.test.tsx
  • packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx
  • packages/react/src/components/presentation/UserProfile/UserProfile.tsx
  • packages/react/src/contexts/ThunderID/ThunderIDContext.ts
  • packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx
  • packages/react/src/contexts/User/UserContext.ts
  • packages/react/src/contexts/User/UserProvider.tsx
  • packages/react/src/contexts/User/useUser.ts
  • packages/react/src/index.ts
  • packages/vue/src/ThunderIDVueClient.ts
  • packages/vue/src/api/getSchemas.ts
  • packages/vue/src/api/getUsersMe.ts
  • packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts
  • packages/vue/src/components/presentation/user-profile/UserProfile.ts
  • packages/vue/src/models/contexts.ts
  • packages/vue/src/providers/ThunderIDProvider.ts
  • packages/vue/src/providers/UserProvider.ts
💤 Files with no reviewable changes (13)
  • packages/react/src/contexts/User/useUser.ts
  • packages/javascript/src/api/tests/getSchemas.test.ts
  • packages/javascript/src/utils/flattenUserSchema.ts
  • packages/javascript/src/models/scim2-schema.ts
  • packages/vue/src/api/getSchemas.ts
  • packages/javascript/src/api/getSchemas.ts
  • packages/javascript/src/utils/tests/generateUserProfile.test.ts
  • packages/javascript/src/utils/generateUserProfile.ts
  • packages/javascript/src/models/user.ts
  • packages/javascript/src/utils/tests/flattenUserSchema.test.ts
  • packages/nextjs/src/server/actions/getUserProfileAction.ts
  • packages/react/src/contexts/User/UserProvider.tsx
  • packages/javascript/src/errors/tests/ThunderIDAPIError.test.ts
✅ Files skipped from review due to trivial changes (7)
  • packages/nuxt/src/runtime/server/routes/auth/user/profile.patch.ts
  • packages/nuxt/src/runtime/server/routes/auth/user/profile.get.ts
  • packages/javascript/src/constants/ScopeConstants.ts
  • packages/javascript/src/utils/processUsername.ts
  • packages/nuxt/src/runtime/types.ts
  • packages/nuxt/tests/unit/thunderid-ssr.test.ts
  • packages/javascript/src/models/config.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/react/package.json
  • packages/javascript/src/theme/createTheme.test.ts
  • packages/react/src/components/auth/Callback/tests/Callback.test.tsx
  • packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts
  • packages/react/src/contexts/ThunderID/ThunderIDProvider.tsx

Comment thread packages/react/src/components/presentation/UserProfile/UserProfile.tsx Outdated
@janithjay janithjay force-pushed the feat/profile-edit-put branch 6 times, most recently from 9320188 to 5bed80c Compare July 6, 2026 07:08
@janithjay janithjay force-pushed the feat/profile-edit-put branch 2 times, most recently from 2a11a2b to f8d9041 Compare July 7, 2026 08:39
@janithjay janithjay changed the title [react] Update profile data edit API endpoints to use PUT /users/me Update profile data edit API endpoints to use PUT /users/me Jul 7, 2026
@janithjay janithjay force-pushed the feat/profile-edit-put branch 3 times, most recently from d4ee7c1 to 1b79521 Compare July 8, 2026 05:25
@janithjay janithjay force-pushed the feat/profile-edit-put branch from 1b79521 to df460d2 Compare July 8, 2026 05:43
authorizeRequestParams.set('client_id', clientId);

authorizeRequestParams.set('scope', scopes ?? '');
if (scopes !== undefined) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@janithjay Any special reason for this change?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In which cases will the scopes be undefined? AFAIR, if the user has not explicitly set the scopes from the config, we internally send openid profile and incase if the user intentionally want to trigger an OAuth flow intended to drop the id_token we should be able to drop the scopes and send the request.

Hope that functionality is intact. @coderabbitai Please validate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "getAuthorizeRequestUrlParams" -C5 --type=ts

Length of output: 6182


🏁 Script executed:

#!/bin/bash
rg -n "getAuthorizeRequestUrlParams\|scopes" -g '*.ts' -g '*.tsx' packages/javascript/src packages/react/src packages/vue/src packages/nuxt/src packages/nextjs/src 2>/dev/null | grep -i "getAuthorizeRequestUrlParams"
echo "---"
rg -n "getAuthorizeRequestUrlParams" -tts 2>/dev/null
echo "---search broader---"
grep -rn "getAuthorizeRequestUrlParams" --include=*.ts --include=*.tsx . 2>/dev/null

Length of output: 636


🏁 Script executed:

#!/bin/bash
fd . -e ts -e tsx | xargs grep -l "getAuthorizeRequestUrlParams" 2>/dev/null

Length of output: 419


🏁 Script executed:

#!/bin/bash
rg -n "getAuthorizeRequestUrlParams|scopes|scope" -B3 -A15 packages/javascript/src/ThunderIDJavaScriptClient.ts | sed -n '1,150p'

Length of output: 5193


🏁 Script executed:

#!/bin/bash
fd processOpenIDScopes.ts | xargs cat -n

Length of output: 3659


🏁 Script executed:

#!/bin/bash
rg -n "scope" -B2 -A10 packages/javascript/src/utils/__tests__/getAuthorizeRequestUrlParams.test.ts | sed -n '1,200p'

Length of output: 2116


🏁 Script executed:

#!/bin/bash
rg -n "DEFAULT_SCOPES" -A4 packages/javascript/src/constants/OIDCRequestConstants.ts

Length of output: 466


@brionmario Verified the behavior end-to-end — the default openid profile injection is intact and unaffected by this line-level change.

Here's the actual call chain:

  1. In ThunderIDJavaScriptClient.ts (Line 470), scopes are always computed via processOpenIDScopes(configData.scopes) before being passed to getAuthorizeRequestUrlParams.
  2. processOpenIDScopes (in packages/javascript/src/utils/processOpenIDScopes.ts) always returns a non-undefined string. If the caller doesn't configure scopes (or passes an empty string/array), it injects OIDCRequestConstants.SignIn.Payload.DEFAULT_SCOPES (['openid', 'profile']) automatically. So the value handed to getAuthorizeRequestUrlParams from this real call site is never undefined.
  3. Because of that, if (scopes !== undefined) always evaluates to true for the ThunderIDJavaScriptClient code path — this diff hunk has zero effect on that flow.

What this diff hunk actually changes is the standalone unit-level behavior of getAuthorizeRequestUrlParams itself: previously, calling it without a scopes value set scope='' (an empty query param) via scopes ?? ''. Now it omits the scope param entirely when scopes is undefined, which is exactly what the new test should set scope to undefined if none provided (Line 150-164 of getAuthorizeRequestUrlParams.test.ts) asserts and documents ("the implementation does not default to 'openid'" at this layer — defaulting is handled upstream by processOpenIDScopes).

Regarding your specific concern about intentionally dropping scopes to suppress the id_token: that capability isn't affected either way by this change, because it's gated entirely by processOpenIDScopes, which currently re-injects DEFAULT_SCOPES whenever userConfiguredScopes is falsy — including when a caller explicitly passes an empty string or empty array. So today there's actually no way to send a truly scope-less authorize request through configData.scopes, but that pre-existing behavior is unrelated to this line-level diff.

Net: the change is safe and scoped only to how getAuthorizeRequestUrlParams behaves when called directly without scopes (avoids an empty scope= param in the URL); it doesn't alter the default openid profile injection behavior used by the SDK's real sign-in flow.


🧬 Code Graph Analysis Results

[snippets]

packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts

Lines ~1-122 (entire file contents)

/**
 * Generates a map of authorization request URL parameters for OIDC authorization requests.
 *
 * This utility ensures the `openid` scope is always included, handles both string and array forms of the `scope` parameter,
 * and supports PKCE and custom parameters. Throws if a code challenge is provided without a code challenge method.
 *
 * `@param` options - The main options for the authorization request, including redirectUri, clientId, scope, responseMode, codeChallenge, codeChallengeMethod, and prompt.
 * `@param` pkceOptions - PKCE options, including the PKCE key for state correlation.
 * `@param` customParams - Optional custom parameters to include in the request (excluding the `state` param, which is handled separately).
 * `@returns` A Map of key-value pairs representing the authorization request URL parameters.
 *
 * `@throws` {ThunderIDRuntimeError} If a code challenge is provided without a code challenge method.
 *
 * `@example`
 * const params = getAuthorizeRequestUrlParams({
 *   options: {
 *     redirectUri: 'https://app/callback',
 *     clientId: 'client123',
 *     scope: ['openid', 'profile'],
 *     responseMode: 'query',
 *     codeChallenge: 'abc',
 *     codeChallengeMethod: 'S256',
 *     prompt: 'login'
 *   },
 *   pkceOptions: { key: 'pkce_code_verifier_1' },
 *   customParams: { foo: 'bar' }
 * });
 * // Returns a Map with all required OIDC params, PKCE, and custom params.
 */
const getAuthorizeRequestUrlParams = (
  options: {
    clientId: string;
    codeChallenge?: string;
    codeChallengeMethod?: string;
    instanceId?: string;
    prompt?: string;
    redirectUri: string;
    responseMode?: string;
    scopes?: string;
  } & ExtendedAuthorizeRequestUrlParams,
  pkceOptions: {key: string},
  customParams: Record<string, string | number | boolean>,
): Map<string, string> => {
  const {redirectUri, clientId, scopes, responseMode, codeChallenge, codeChallengeMethod, prompt} = options;
  const authorizeRequestParams: Map<string, string> = new Map<string, string>();

  authorizeRequestParams.set('response_type', 'code');
  authorizeRequestParams.set('client_id', clientId);

  if (scopes !== undefined) {
    authorizeRequestParams.set('scope', scopes);
  }
  authorizeRequestParams.set('redirect_uri', redirectUri);

  if (responseMode) {
    authorizeRequestParams.set('response_mode', responseMode);
  }

  const pkceKey: string = pkceOptions?.key;

  if (codeChallenge) {
    authorizeRequestParams.set('code_challenge', codeChallenge);

    if (codeChallengeMethod) {
      authorizeRequestParams.set('code_challenge_method', codeChallengeMethod);
    } else {
      throw new ThunderIDRuntimeError(
        'Code challenge method is required when code challenge is provided.',
        'getAuthorizeRequestUrlParams-ValidationError-001',
        'javascript',
        'When PKCE is enabled, the code challenge method must be provided along with the code challenge.',
      );
    }
  }

  if (prompt) {
    authorizeRequestParams.set('prompt', prompt);
  }

  if (customParams) {
    Object.entries(customParams).forEach(([key, value]: [string, string | number | boolean]) => {
      if (key !== '' && value !== '' && key !== OIDCRequestConstants.Params.STATE) {
        authorizeRequestParams.set(key, value.toString());
      }
    });
  }

  const AUTH_INSTANCE_PREFIX = 'instance_';
  let customStateValue = '';

  if (options.instanceId) {
    customStateValue = AUTH_INSTANCE_PREFIX + options.instanceId;
  } else if (customParams) {
    customStateValue = customParams[OIDCRequestConstants.Params.STATE]?.toString() ?? '';
  }

  authorizeRequestParams.set(
    OIDCRequestConstants.Params.STATE,
    generateStateParamForRequestCorrelation(pkceKey, customStateValue),
  );

  return authorizeRequestParams;
};

export default getAuthorizeRequestUrlParams;

packages/javascript/src/models/oauth-request.ts

Lines ~26-27

export type ExtendedAuthorizeRequestUrlParams = KnownExtendedAuthorizeRequestUrlParams &
  Record<string, string | boolean>;

packages/javascript/src/utils/generateStateParamForRequestCorrelation.ts

Lines ~36-40

generateStateParamForRequestCorrelation = (pkceKey: string, state?: string): string => {
  const index: number = parseInt(pkceKey.split(PKCEConstants.Storage.StorageKeys.SEPARATOR)[1], 10);

  return state ? `${state}_request_${index}` : `request_${index}`;
}

packages/javascript/src/errors/ThunderIDRuntimeError.ts

Lines ~35-68

export default class ThunderIDRuntimeError extends ThunderIDError {
  /**
   * Creates an instance of ThunderIDRuntimeError.
   *
   * `@param` message - Human-readable description of the error
   * `@param` code - A unique error code that identifies the error type
   * `@param` details - Additional details about the error that might be helpful for debugging
   * `@param` origin - Optional. The SDK origin (e.g. 'react', 'vue'). Defaults to generic 'ThunderID'
   * `@constructor`
   */
  constructor(
    message: string,
    code: string,
    origin: string,
    public readonly details?: unknown,
  ) {
    super(message, code, origin);

    Object.defineProperty(this, 'name', {
      configurable: true,
      value: 'ThunderIDRuntimeError',
      writable: true,
    });
  }

  /**
   * Returns a string representation of the runtime error
   * `@returns` Formatted error string with name, code, details, and message
   */
  public override toString(): string {
    const details: string = this.details ? `\nDetails: ${JSON.stringify(this.details, null, 2)}` : '';
    return `[${this.name}] (code="${this.code}")${details}\nMessage: ${this.message}`;
  }
}

✏️ Learnings added
Learnt from: brionmario
Repo: thunder-id/javascript-sdks PR: 9
File: packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts:73-73
Timestamp: 2026-07-08T11:09:21.512Z
Learning: In `packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts`, the `scope` URL parameter is set only when `options.scopes` is not `undefined`; there is no local default-scope injection at this layer. Default scope injection (`openid profile` via `OIDCRequestConstants.SignIn.Payload.DEFAULT_SCOPES`) happens upstream in `packages/javascript/src/utils/processOpenIDScopes.ts`, which is always called before `getAuthorizeRequestUrlParams` in `ThunderIDJavaScriptClient.ts` and always returns a non-undefined string, forcing default scopes whenever the caller doesn't configure non-empty scopes (even explicit empty string/array is treated as unconfigured).

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

flattenedProfile={flattenedProfile!}
schemas={schemas!}
onUpdate={handleProfileUpdate}
editable={isEditableProfile}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would prefer to have readOnly as the flag instead of editable.
@JayaShakthi97

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Is there schemas support

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

No need to again get userprofile for update

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

need to remove function and the test

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Check and revert

const {preferences: contextPreferences} = useThunderID();
const {profile, flattenedProfile, onUpdateProfile, updateProfile} = useUser();
const resolvedPreferences = {
...contextPreferences,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

try to use deepmerge

@@ -38,7 +38,6 @@ const getUserProfileAction = async (
userProfile: {
flattenedProfile: {},

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

no need flattened profile

profile: {},
schemas: [],
};

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

use deepmerge

const message: string = error instanceof Error ? error.message : String(error);

if (!message.includes('end_session_endpoint')) {
if (

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

need to revert, out of scope

const nextUser = flattenedProfile ?? profile;
if (!nextUser) return;

setEditedUser((prev: any) => {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

need to add reply for review


// Fields that should be readonly
const readonlyFields: string[] = ['username', 'userName', 'user_name'];
const readonlyFields: string[] = ['attributes', 'id', 'isReadOnly', 'ouId', 'username'];

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

need to check do we need to hardcode this or derive from api

const {baseUrl, instanceId, preferences: contextPreferences} = useThunderID();
const {profile, flattenedProfile, onUpdateProfile} = useUser();
const resolvedPreferences = {
...contextPreferences,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

need to use deepmerge

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@coderabbitai Create a git issue to evaluate api functions in react package

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="thunder-id/javascript-sdks"

gh issue create \
  --repo "$repo" \
  --title "Evaluate API functions in the react package" \
  --assignee "janithjay" \
  --body "$(cat <<'EOF'
## Summary

Following up on PR `#9`, which migrated the `packages/react` SDK's profile self-service flow from SCIM-based `/scim2/Me` requests to standard `/users/me` requests, we should evaluate the API functions in `packages/react/src/api` to ensure the surface is consistent, well-documented, and free of dead code after the migration.

## Rationale

The migration changed several exported functions/interfaces (e.g. `getUsersMe`, `updateMeProfile`, and the `getMeProfile` re-export in `packages/react/src/index.ts`), and removed SCIM2 schema-related types and exports. A dedicated pass would help confirm:

- Naming consistency between the React-specific wrappers (e.g. `getUsersMe` in `packages/react/src/api/getUsersMe.ts`) and the underlying `@thunderid/browser` functions they wrap.
- Whether default fetcher implementations (e.g. `defaultFetcher` in `packages/react/src/api/getUsersMe.ts` and `packages/react/src/api/updateMeProfile.ts`) are duplicated unnecessarily across files and could be extracted into a shared helper.
- Whether all exported API functions/types in `packages/react/src/api` are still referenced from `packages/react/src/index.ts` and used correctly by consumers (e.g. `BaseUserProfile.tsx`, `UserProfile.tsx`, `ThunderIDProvider.tsx`).
- Whether JSDoc examples and error-handling behavior (headers, status codes, `ThunderIDAPIError` usage) are consistent across all API functions post-migration.

## Affected areas

- `packages/react/src/api/getUsersMe.ts`
- `packages/react/src/api/updateMeProfile.ts`
- `packages/react/src/index.ts`

## Acceptance criteria

- [ ] Review all exported functions/types under `packages/react/src/api` for consistency in naming, headers, error handling, and documentation.
- [ ] Identify and consolidate duplicated logic (e.g. default fetcher adapters) where feasible.
- [ ] Confirm all public exports are still needed and correctly wired to consumers.
- [ ] File follow-up PRs for any concrete refactors identified.

## References

- PR: https://github.com/thunder-id/javascript-sdks/pull/9
- Related comment: https://github.com/thunder-id/javascript-sdks/pull/9#discussion_r3549545309

Requested by: `@janithjay`
EOF
)"

Length of output: 9739


Issues are disabled on the thunder-id/javascript-sdks repository, so I wasn't able to create the issue via GitHub CLI.

@janithjay, I attempted to create the follow-up issue but the repository has issues disabled. Could you enable issues for this repository (Settings → General → Features → Issues), or let me know if you'd like this tracked elsewhere (e.g., a different repo, or a project board)? Once issues are enabled, I can create it right away with the following content:

Title: Evaluate API functions in the react package

Body summary:

  • Review naming consistency between React-specific wrappers (getUsersMe in packages/react/src/api/getUsersMe.ts, updateMeProfile in packages/react/src/api/updateMeProfile.ts) and underlying @thunderid/browser functions.
  • Consolidate duplicated defaultFetcher adapter logic across getUsersMe.ts and updateMeProfile.ts (and similarly in packages/vue/src/api/getUsersMe.ts) into a shared helper.
  • Confirm all exported API functions/types in packages/react/src/api are still referenced correctly in packages/react/src/index.ts and consumers (BaseUserProfile.tsx, UserProfile.tsx, ThunderIDProvider.tsx).
  • Verify JSDoc examples and error-handling consistency post /users/me migration.
  • References: PR #9, comment Update profile data edit API endpoints to use PUT /users/me #9 (comment)

Let me know how you'd like to proceed.


🧬 Code Graph Analysis Results

packages/javascript/src/api/getUsersMe.ts

(Lines 93-157)

getUsersMe = async ({url, baseUrl, fetcher, ...requestConfig}: GetUsersMeConfig): Promise<User> => {
  try {
    // eslint-disable-next-line no-new
    new URL((url ?? baseUrl)!);
  } catch (error) {
    throw new ThunderIDAPIError(
      `Invalid URL provided. ${error?.toString()}`,
      'getUsersMe-ValidationError-001',
      'javascript',
      400,
      'The provided `url` or `baseUrl` path does not adhere to the URL schema.',
    );
  }

  const fetchFn: typeof fetch = fetcher || fetch;
  const resolvedUrl: string = url ?? `${baseUrl}/users/me`;

  const requestInit: RequestInit = {
    ...requestConfig,
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      ...requestConfig.headers,
    },
    method: 'GET',
  };

  try {
    const response: Response = await fetchFn(resolvedUrl, requestInit);

    if (!response?.ok) {
      const errorText: string = await response.text();

      throw new ThunderIDAPIError(
        errorText,
        'getUsersMe-ResponseError-001',
        'javascript',
        response.status,
        response.statusText,
        'Failed to fetch user profile',
      );
    }

    const user: User = (await response.json()) as User;
    const attributes: Record<string, unknown> = (user['attributes'] as Record<string, unknown>) ?? {};
    const processedUser: User = {
      ...user,
      ...attributes,
    };

    return processUserUsername(processedUser);
  } catch (error) {
    if (error instanceof ThunderIDAPIError) {
      throw error;
    }

    throw new ThunderIDAPIError(
      `Network or parsing error: ${error instanceof Error ? error.message : 'Unknown error'}`,
      'getUsersMe-NetworkError-001',
      'javascript',
      0,
      'Network Error',
    );
  }
}

packages/browser/src/FetchHttpClient.ts

(Lines 29-131)

protected async transport<T = any>(config: HttpRequestConfig): Promise<HttpResponse<T>> {
  const {
    attachToken,
    data,
    headers: configHeaders,
    method,
    params,
    shouldAttachIDPAccessToken,
    shouldEncodeToFormData,
    startTimeInMs,
    url: configUrl,
    ...fetchOptions
  } = config;

  let url: string = configUrl ?? '';

  if (params) {
    const qs: string = new URLSearchParams(params).toString();
    if (qs) {
      url = `${url}${url.includes('?') ? '&' : '?'}${qs}`;
    }
  }

  const headers: Record<string, string> = {...(configHeaders ?? {})};
  let body: BodyInit | undefined;

  if (data !== undefined) {
    if (data instanceof FormData) {
      body = data;
    } else {
      body = JSON.stringify(data);
      if (!headers['Content-Type'] && !headers['content-type']) {
        headers['Content-Type'] = 'application/json';
      }
    }
  }

  let fetchResponse: Response;

  try {
    fetchResponse = await fetch(url, {
      credentials: 'include',
      ...fetchOptions,
      body,
      headers,
      method: (method ?? 'GET').toUpperCase(),
    });
  } catch (networkError: any) {
    throw Object.assign(new Error(networkError.message), {
      code: 'NETWORK_ERROR',
      config,
    } as Partial<HttpError>);
  }

  const contentType: string = fetchResponse.headers.get('content-type') ?? '';
  const responseData: T = contentType.includes('application/json')
    ? await fetchResponse.json()
    : ((await fetchResponse.text()) as any);

  const responseHeaders: Record<string, string> = {};
  fetchResponse.headers.forEach((value: string, key: string) => {
    responseHeaders[key] = value;
  });

  if (!fetchResponse.ok) {
    throw Object.assign(new Error(fetchResponse.statusText), {
      config,
      response: {
        data: responseData,
        headers: responseHeaders,
        status: fetchResponse.status,
        statusText: fetchResponse.statusText,
      },
    } as Partial<HttpError>);
  }

  return {
    config,
    data: responseData,
    headers: responseHeaders,
    status: fetchResponse.status,
    statusText: fetchResponse.statusText,
  };
}

packages/browser/src/FetchHttpClient.ts

(Lines 32-41)

static getInstance(
  instanceId = 0,
  isHandlerEnabled = true,
  attachToken: (request: HttpRequestConfig) => Promise<void> = (): Promise<void> => Promise.resolve(),
): FetchHttpClient {
  if (!this.instances.has(instanceId)) {
    this.instances.set(instanceId, new FetchHttpClient(isHandlerEnabled, attachToken));
  }
  return this.instances.get(instanceId)!;
}

(Lines 43-45)

static destroyInstance(instanceId = 0): void {
  this.instances.delete(instanceId);
}

packages/javascript/src/models/http.ts

(Lines 19-25)

export interface HttpResponse<T = any> {
  config: HttpRequestConfig;
  data: T;
  headers: Record<string, string>;
  status: number;
  statusText: string;
}

(Lines 38-48)

export interface HttpRequestConfig extends Omit<RequestInit, 'body' | 'headers' | 'method'> {
  attachToken?: boolean;
  data?: any;
  headers?: Record<string, string>;
  method?: string;
  params?: Record<string, any>;
  shouldAttachIDPAccessToken?: boolean;
  shouldEncodeToFormData?: boolean;
  startTimeInMs?: number;
  url?: string;
}

packages/javascript/src/models/user.ts

(Lines 27-29)

export interface User extends KnownUser {
  [key: string]: any;
}

packages/vue/src/api/getUsersMe.ts

(Lines 34-50)

defaultFetcher = async (url: string, config: RequestInit): Promise<Response> => {
  const httpClient: FetchHttpClient = FetchHttpClient.getInstance(instanceId);

  const response: HttpResponse<any> = await httpClient.request({
    headers: config.headers as Record<string, string>,
    method: config.method || 'GET',
    url,
  } as HttpRequestConfig);

  return {
    json: () => Promise.resolve(response.data),
    ok: response.status >= 200 && response.status < 300,
    status: response.status,
    statusText: response.statusText || '',
    text: () => Promise.resolve(typeof response.data === 'string' ? response.data : JSON.stringify(response.data)),
  } as Response;
}

packages/react/src/api/updateMeProfile.ts

(Lines 69-85)

defaultFetcher = async (url: string, config: RequestInit): Promise<Response> => {
  const httpClient: FetchHttpClient = FetchHttpClient.getInstance(instanceId);
  const response: HttpResponse<any> = await httpClient.request({
    data: config.body ? JSON.parse(config.body as string) : undefined,
    headers: config.headers as Record<string, string>,
    method: config.method || 'PUT',
    url,
  } as HttpRequestConfig);

  return {
    json: () => Promise.resolve(response.data),
    ok: response.status >= 200 && response.status < 300,
    status: response.status,
    statusText: response.statusText || '',
    text: () => Promise.resolve(typeof response.data === 'string' ? response.data : JSON.stringify(response.data)),
  } as Response;
}

onUpdate?: (payload: any) => Promise<void>;
profile?: User | null;
schemas?: Schema[] | null;
schemas?: any[] | null;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

need to check schemas and review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[React SDK] Enable self-service profile updates [Next.js SDK] Enable self-service profile updates

4 participants