Skip to content

Enable self-service profile updates in iOS SDKs - #20

Merged
brionmario merged 1 commit into
thunder-id:mainfrom
janithjay:users-me-profile
Aug 28, 2026
Merged

Enable self-service profile updates in iOS SDKs#20
brionmario merged 1 commit into
thunder-id:mainfrom
janithjay:users-me-profile

Conversation

@janithjay

Copy link
Copy Markdown
Contributor

Purpose

Currently, the iOS SDK's user profile view sources attributes from the JWT/userinfo claims embedded in the sign-in token, which is read-only and limited to whatever claims a given application's token happens to carry.

This PR makes the profile view fetch from GET /users/me by default, matching the JavaScript SDK, and adds,

  • Self-service editing per attribute, validated and saved against the schema from GET /users/me/meta (PUT /users/me).
  • Avatar picture and display-name sync from the real profile, kept in sync with ThunderIDState.user across sign-in/refresh.
  • An opt-out config flag, ThunderIDConfig.fetchUserProfile (default true), for apps that want the previous token-claims-only, read-only behavior instead.

Approach

  • ThunderIDClient: fixed getUserProfile() / added getUserSchema() to call the real /users/me / /users/me/meta endpoints (previously pointed at nonexistent /scim2/* routes). updateUserProfile() now does PUT /users/me with {"attributes": {...}}, matching the backend's actual contract and the JavaScript SDK's updateMeProfile.
  • Access token provider: the provider required a clientId before it would return a token, but app-native (Flow Execution) sessions never have one, so every authenticated request failed with SDK_NOT_INITIALIZED before reaching the server. The guard also contradicted TokenRefresher.getAccessToken(clientId:), which is explicitly documented to work with a nil clientId while the cached token is valid. Required for /users/me to be reachable at all.
  • UserProfile.swift: schema-driven field building (buildProfileFields), validation (validateField: required + regex, invalid regex ignored rather than blocking save), and a merge-before-save step (deepMergeAttributes) since the backend rejects a save missing any required attribute, even for a single-field edit. deepUnwrapAttributes unwraps AnyCodable boxes before the PUT, since JSONSerialization raises an uncatchable exception on non-JSON types.
  • Token-only mode (fetchUserProfile = false) intentionally mirrors the Quickstart's pre-existing format() / label(for:) / displayName behavior verbatim rather than reinventing it, now hoisted from the sample into the reusable SDK component.
  • ThunderIDState: the /users/me sync is launched in a detached Task and its failures are swallowed, matching the JavaScript SDK's provider, which logs a warning rather than surfacing a profile-fetch failure as an auth error. Unlike Android, no provider-level scope hoisting is needed: refresh() is called from plain Task {} blocks in button handlers rather than from a .task {} view modifier, so there is no view-lifetime cancellation to work around.
  • Edit/save/cancel controls use SF Symbols (pencil, checkmark.circle.fill, xmark.circle.fill), so no new image assets were added.

The data-contract pieces are ported to match the JavaScript SDK exactly: the readonlyFields list, the defaultAttributeMappings fallback order, the readonly derivation (readOnly == true || mutability == "READ_ONLY" || readonlyFields.contains(key)), the required-then-regex validation order, and the {attributes: ...} PUT payload.

After fix default method (fetchUserProfile = true)

image

After fix attributes fetch from sign-in token method (fetchUserProfile = false)

image

Related Issues

Related PRs

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.
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Copilot AI lite review requested due to automatic review settings August 26, 2026 08:39

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 commented Aug 26, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d534579-e749-43e9-b8d7-0a9003a49259


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.

Signed-off-by: janithjay <janithjayashan018@gmail.com>
@brionmario
brionmario merged commit aca22a7 into thunder-id:main Aug 28, 2026
8 checks passed
@janithjay
janithjay deleted the users-me-profile branch August 28, 2026 06:23
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.

3 participants