Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/V9_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,31 @@ This is intended to make strict TypeScript error handling easier, especially if
The v9 update also tightens webhook/event deserialization behavior for unknown event types.

If your integration assumed unknown event types would be ignored or treated loosely, re-test webhook handling on the latest v9 release.

### User Management

#### `getUserIdentities` now normalizes `GithubOAuth` to `GitHubOAuth`

The WorkOS API returns `GithubOAuth` (lowercase 'h') as the provider in identity responses, but `getAuthorizationUrl` expects `GitHubOAuth` (uppercase 'H'). The SDK now normalizes the casing during deserialization so that identity provider values can be passed directly to `getAuthorizationUrl`.

If your code was comparing against the raw API value `'GithubOAuth'`, update it to `'GitHubOAuth'`:

**Before (v8):**

```typescript
const identities = await workos.userManagement.getUserIdentities(userId);
const github = identities.find((i) => i.provider === 'GithubOAuth');
```

**After (v9):**

```typescript
const identities = await workos.userManagement.getUserIdentities(userId);
const github = identities.find((i) => i.provider === 'GitHubOAuth');

// The provider value can now be passed directly to getAuthorizationUrl
const url = workos.userManagement.getAuthorizationUrl({
provider: github.provider,
redirectUri: 'https://example.com/callback',
});
```
13 changes: 13 additions & 0 deletions src/user-management/interfaces/identity-response.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
interface RawIdentityResponse {
idp_id: string;
type: 'OAuth';
provider:
| 'AppleOAuth'
| 'GithubOAuth'
| 'GitHubOAuth'
| 'GoogleOAuth'
| 'MicrosoftOAuth'
| 'SalesforceOAuth';
}

export type { RawIdentityResponse };
11 changes: 0 additions & 11 deletions src/user-management/interfaces/identity.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,3 @@ export interface Identity {
| 'MicrosoftOAuth'
| 'SalesforceOAuth';
}

export interface IdentityResponse {
idp_id: string;
type: 'OAuth';
provider:
| 'AppleOAuth'
| 'GoogleOAuth'
| 'GitHubOAuth'
| 'MicrosoftOAuth'
| 'SalesforceOAuth';
}
25 changes: 25 additions & 0 deletions src/user-management/serializers/identity.serializer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { deserializeIdentities } from './identity.serializer';

describe('deserializeIdentities', () => {
it('normalizes GithubOAuth to GitHubOAuth', () => {
const result = deserializeIdentities([
{ idp_id: '123', type: 'OAuth', provider: 'GithubOAuth' },
]);

expect(result).toEqual([
{ idpId: '123', type: 'OAuth', provider: 'GitHubOAuth' },
]);
});

it('leaves other providers unchanged', () => {
const result = deserializeIdentities([
{ idp_id: '456', type: 'OAuth', provider: 'GoogleOAuth' },
{ idp_id: '789', type: 'OAuth', provider: 'AppleOAuth' },
]);

expect(result).toEqual([
{ idpId: '456', type: 'OAuth', provider: 'GoogleOAuth' },
{ idpId: '789', type: 'OAuth', provider: 'AppleOAuth' },
]);
});
});
19 changes: 16 additions & 3 deletions src/user-management/serializers/identity.serializer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { Identity, IdentityResponse } from '../interfaces/identity.interface';
import { Identity } from '../interfaces/identity.interface';
import { RawIdentityResponse } from '../interfaces/identity-response.interface';

// The API returns 'GithubOAuth' but getAuthorizationUrl expects 'GitHubOAuth'.
// Normalize here so callers can pass identity.provider directly.
// See: https://github.com/workos/workos-node/issues/1227
const normalizeProvider = (
provider: RawIdentityResponse['provider'],
): Identity['provider'] => {
if (provider === 'GithubOAuth') {
return 'GitHubOAuth';
}
return provider;
};

export const deserializeIdentities = (
identities: IdentityResponse[],
identities: RawIdentityResponse[],
): Identity[] => {
return identities.map((identity) => {
return {
idpId: identity.idp_id,
type: identity.type,
provider: identity.provider,
provider: normalizeProvider(identity.provider),
};
});
};
20 changes: 19 additions & 1 deletion src/user-management/user-management.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1732,7 +1732,7 @@ describe('UserManagement', () => {
{
idpId: '108872335',
type: 'OAuth',
provider: 'GithubOAuth',
provider: 'GitHubOAuth',
},
{
idpId: '111966195055680542408',
Expand All @@ -1741,6 +1741,24 @@ describe('UserManagement', () => {
},
]);
});

it('normalizes GithubOAuth to GitHubOAuth so it can be passed to getAuthorizationUrl', async () => {
fetchOnce(identityFixture);

const identities = await workos.userManagement.getUserIdentities(userId);

const githubIdentity = identities.find(
(i) => i.provider === 'GitHubOAuth',
);
expect(githubIdentity).toBeDefined();

const url = workos.userManagement.getAuthorizationUrl({
provider: githubIdentity!.provider,
redirectUri: 'https://example.com/callback',
});

expect(url).toContain('provider=GitHubOAuth');
});
});

describe('getOrganizationMembership', () => {
Expand Down
5 changes: 3 additions & 2 deletions src/user-management/user-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ import {
CreateOrganizationMembershipOptions,
SerializedCreateOrganizationMembershipOptions,
} from './interfaces/create-organization-membership-options.interface';
import { Identity, IdentityResponse } from './interfaces/identity.interface';
import { Identity } from './interfaces/identity.interface';
import { RawIdentityResponse } from './interfaces/identity-response.interface';
import {
Invitation,
InvitationResponse,
Expand Down Expand Up @@ -812,7 +813,7 @@ export class UserManagement {
throw new TypeError(`Incomplete arguments. Need to specify 'userId'.`);
}

const { data } = await this.workos.get<IdentityResponse[]>(
const { data } = await this.workos.get<RawIdentityResponse[]>(
`/user_management/users/${userId}/identities`,
);

Expand Down
Loading