Skip to content

Commit 302fcf5

Browse files
authored
Adds check to password change functionality to see if identity is federated. If so, the Change Password option is hidden from the user drop-down. Also updates the AUTH_SSO docs to include instructions for setting up the identity protocol mapper.
1 parent d154946 commit 302fcf5

4 files changed

Lines changed: 48 additions & 8 deletions

File tree

apps/editor/src/app/providers/AuthProvider.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ type AuthContextValue = {
1616
completeSignIn: (_callbackUrl?: string) => Promise<void>
1717
signOut: () => Promise<void>
1818
getAccessToken: () => Promise<string | null>
19-
/** Redirect to Keycloak's password change form via kc_action=UPDATE_PASSWORD */
20-
changePassword: () => Promise<void>
19+
/**
20+
* Redirect to Keycloak's password change form via kc_action=UPDATE_PASSWORD.
21+
* Null for federated users (external IdP) who do not have a Keycloak-managed password.
22+
*/
23+
changePassword: (() => Promise<void>) | null
2124
}
2225

2326
const STORAGE_KEY_TENANT = 'case-editor:auth:tenantId'
@@ -36,6 +39,12 @@ function writeTenantId(tenantId: string) {
3639
}
3740
}
3841

42+
function isFederatedUser(user: User | null): boolean {
43+
const profile = user?.profile as Record<string, unknown> | undefined
44+
const idp = profile?.identity_provider
45+
return typeof idp === 'string' && idp.trim().length > 0
46+
}
47+
3948
function pickUserName(user: User | null): string | null {
4049
const p = user?.profile as Record<string, unknown> | undefined
4150
if (!p) return null
@@ -207,12 +216,14 @@ export function AuthProvider({ children }: Readonly<{ children: ReactNode }>) {
207216
return u.access_token
208217
}, [userManager])
209218

210-
const changePassword = useCallback(async () => {
219+
const changePasswordFn = useCallback(async () => {
211220
await userManager.signinRedirect({
212221
extraQueryParams: { kc_action: 'UPDATE_PASSWORD' },
213222
})
214223
}, [userManager])
215224

225+
const changePassword = isFederatedUser(user) ? null : changePasswordFn
226+
216227
const value: AuthContextValue = useMemo(
217228
() => ({
218229
status,

apps/editor/src/ui/editor/EditorCanvas.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1124,7 +1124,7 @@ export default function EditorCanvas({ onBack, onSaveToServer, isPublishedToOpen
11241124
frameworkSubtitle={frameworkInfo.subtitle}
11251125
userName={userName ?? undefined}
11261126
tenantId={tenantId ?? undefined}
1127-
onChangePassword={authStatus === 'authenticated' ? () => void changePassword() : undefined}
1127+
onChangePassword={authStatus === 'authenticated' && changePassword ? () => void changePassword() : undefined}
11281128
reserveRightForPanel={Boolean(selectedNode || selectedEdge || (selectedNodeIds.length + selectedEdgeIds.length > 1))}
11291129
showSettings
11301130
isDirty={isDirty}

apps/editor/src/ui/home/HomeScreen.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ export default function HomeScreen({
472472
</svg>
473473

474474
{/* User button — top right */}
475-
<UserAvatarMenu userName={userName ?? undefined} tenantId={tenantId ?? undefined} isAuthenticated={isAuthenticated} onSignOut={isAuthenticated ? () => void signOut() : undefined} onChangePassword={isAuthenticated ? () => void changePassword() : undefined} onApiKeys={isAuthenticated ? () => setApiKeysOpen(true) : undefined} />
475+
<UserAvatarMenu userName={userName ?? undefined} tenantId={tenantId ?? undefined} isAuthenticated={isAuthenticated} onSignOut={isAuthenticated ? () => void signOut() : undefined} onChangePassword={isAuthenticated && changePassword ? () => void changePassword() : undefined} onApiKeys={isAuthenticated ? () => setApiKeysOpen(true) : undefined} />
476476

477477
<div className="relative mx-auto max-w-6xl px-5 pb-12 pt-14">
478478
<div className="flex items-baseline gap-3">

docs/AUTH0_SSO.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,36 @@ Click **Save** after each mapper.
156156

157157
---
158158

159-
## Step 4 -- Configure First Login Flow (Optional)
159+
## Step 4 -- Expose Identity Provider in the Token
160+
161+
By default Keycloak does not include which identity provider a user authenticated through in the tokens it issues. OpenCASE uses this information to hide the **Change Password** option for federated users — users whose password is managed by Auth0 rather than Keycloak cannot change it through Keycloak, so the menu item is suppressed when this claim is present.
162+
163+
Add a **User Session Note** mapper to the OIDC client (or to a shared client scope so it applies to all tenants automatically):
164+
165+
1. In the Keycloak Admin Console, go to **Clients**`tenant-{id}`**Client scopes** tab
166+
2. Click the client's dedicated scope (e.g. `tenant-{id}-dedicated`)
167+
3. Go to the **Mappers** tab → **Add mapper****By configuration****User Session Note**
168+
4. Fill in the following fields:
169+
170+
| Field | Value |
171+
|---|---|
172+
| **Name** | `identity-provider` |
173+
| **Session note** | `identity_provider` |
174+
| **Token claim name** | `identity_provider` |
175+
| **Claim JSON type** | `String` |
176+
| **Add to ID token** | On |
177+
| **Add to access token** | Off |
178+
| **Add to userinfo** | Off |
179+
180+
5. Click **Save**
181+
182+
> **Shared scope alternative:** If you add this mapper to the built-in `profile` client scope instead, it applies to every tenant client without repeating the step for each one. Navigate to **Client scopes** (realm-level, not client-level) → `profile`**Mappers****Add mapper**.
183+
184+
After this change, federated users will have `"identity_provider": "auth0"` (or the alias of whatever IdP they used) in their ID token. OpenCASE reads this claim to determine whether to offer the Change Password menu item — native Keycloak users have no such claim and will continue to see the option as before.
185+
186+
---
187+
188+
## Step 5 -- Configure First Login Flow (Optional)
160189

161190
When a user logs in via Auth0 for the first time, Keycloak's **First Broker Login** flow determines what happens. The default flow:
162191

@@ -172,7 +201,7 @@ This works well for most setups. If you want to **skip the review page** and cre
172201

173202
---
174203

175-
## Step 5 -- Test the Integration
204+
## Step 6 -- Test the Integration
176205

177206
1. Open your OpenCASE Editor: `https://YOUR_DOMAIN`
178207
2. Click **Sign in**
@@ -185,7 +214,7 @@ This works well for most setups. If you want to **skip the review page** and cre
185214
186215
---
187216

188-
## Step 6 -- Assign Roles to SSO Users
217+
## Step 7 -- Assign Roles to SSO Users
189218

190219
Users who log in via Auth0 are created in Keycloak with no roles by default. To give them access to OpenCASE features, you need to assign roles:
191220

0 commit comments

Comments
 (0)