From c052d2e1580107b43c406561f6d335a2d7be1acb Mon Sep 17 00:00:00 2001 From: "jeff.fiddler" Date: Fri, 24 Jul 2026 21:46:09 +0000 Subject: [PATCH 1/3] Fix TypeError when listing non-TOTP user auth factors Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../fixtures/list-factors.json | 22 +++++++++++++++- .../multi-factor-auth.spec.ts | 20 ++++++++++++++ .../fixtures/list-factors.json | 22 +++++++++++++++- .../authentication-factor.interface.ts | 26 +++++++++++++------ .../authentication-factor.serializer.ts | 7 +++-- 5 files changed, 85 insertions(+), 12 deletions(-) diff --git a/src/multi-factor-auth/fixtures/list-factors.json b/src/multi-factor-auth/fixtures/list-factors.json index 288833151..1135a300e 100644 --- a/src/multi-factor-auth/fixtures/list-factors.json +++ b/src/multi-factor-auth/fixtures/list-factors.json @@ -10,7 +10,27 @@ "totp": { "issuer": "WorkOS", "user": "some_user" - } + }, + "user_id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS" + }, + { + "object": "authentication_factor", + "id": "auth_factor_5678", + "created_at": "2022-03-15T20:39:19.892Z", + "updated_at": "2022-03-15T20:39:19.892Z", + "type": "sms", + "sms": { + "phone_number": "+15555555555" + }, + "user_id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS" + }, + { + "object": "authentication_factor", + "id": "auth_factor_9012", + "created_at": "2022-03-15T20:39:19.892Z", + "updated_at": "2022-03-15T20:39:19.892Z", + "type": "generic_otp", + "user_id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS" } ], "list_metadata": { diff --git a/src/multi-factor-auth/multi-factor-auth.spec.ts b/src/multi-factor-auth/multi-factor-auth.spec.ts index 2f04c5ade..f2676d5f7 100644 --- a/src/multi-factor-auth/multi-factor-auth.spec.ts +++ b/src/multi-factor-auth/multi-factor-auth.spec.ts @@ -520,6 +520,26 @@ describe('MFA', () => { issuer: 'WorkOS', user: 'some_user', }, + userId: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS', + }, + { + object: 'authentication_factor', + id: 'auth_factor_5678', + createdAt: '2022-03-15T20:39:19.892Z', + updatedAt: '2022-03-15T20:39:19.892Z', + type: 'sms', + sms: { + phoneNumber: '+15555555555', + }, + userId: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS', + }, + { + object: 'authentication_factor', + id: 'auth_factor_9012', + createdAt: '2022-03-15T20:39:19.892Z', + updatedAt: '2022-03-15T20:39:19.892Z', + type: 'generic_otp', + userId: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS', }, ], listMetadata: { diff --git a/src/user-management/fixtures/list-factors.json b/src/user-management/fixtures/list-factors.json index 288833151..1135a300e 100644 --- a/src/user-management/fixtures/list-factors.json +++ b/src/user-management/fixtures/list-factors.json @@ -10,7 +10,27 @@ "totp": { "issuer": "WorkOS", "user": "some_user" - } + }, + "user_id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS" + }, + { + "object": "authentication_factor", + "id": "auth_factor_5678", + "created_at": "2022-03-15T20:39:19.892Z", + "updated_at": "2022-03-15T20:39:19.892Z", + "type": "sms", + "sms": { + "phone_number": "+15555555555" + }, + "user_id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS" + }, + { + "object": "authentication_factor", + "id": "auth_factor_9012", + "created_at": "2022-03-15T20:39:19.892Z", + "updated_at": "2022-03-15T20:39:19.892Z", + "type": "generic_otp", + "user_id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS" } ], "list_metadata": { diff --git a/src/user-management/interfaces/authentication-factor.interface.ts b/src/user-management/interfaces/authentication-factor.interface.ts index fb1732e04..2a68da172 100644 --- a/src/user-management/interfaces/authentication-factor.interface.ts +++ b/src/user-management/interfaces/authentication-factor.interface.ts @@ -1,3 +1,7 @@ +import { + Sms, + SmsResponse, +} from '../../multi-factor-auth/interfaces/sms.interface'; import { Totp, TotpResponse, @@ -5,13 +9,16 @@ import { TotpWithSecretsResponse, } from '../../multi-factor-auth/interfaces/totp.interface'; +export type AuthenticationFactorType = 'sms' | 'totp' | 'generic_otp'; + export interface AuthenticationFactor { object: 'authentication_factor'; id: string; createdAt: string; updatedAt: string; - type: 'totp'; - totp: Totp; + type: AuthenticationFactorType; + sms?: Sms; + totp?: Totp; userId: string; } @@ -20,8 +27,9 @@ export interface AuthenticationFactorWithSecrets { id: string; createdAt: string; updatedAt: string; - type: 'totp'; - totp: TotpWithSecrets; + type: AuthenticationFactorType; + sms?: Sms; + totp?: TotpWithSecrets; userId: string; } @@ -30,8 +38,9 @@ export interface AuthenticationFactorResponse { id: string; created_at: string; updated_at: string; - type: 'totp'; - totp: TotpResponse; + type: AuthenticationFactorType; + sms?: SmsResponse; + totp?: TotpResponse; user_id: string; } @@ -40,7 +49,8 @@ export interface AuthenticationFactorWithSecretsResponse { id: string; created_at: string; updated_at: string; - type: 'totp'; - totp: TotpWithSecretsResponse; + type: AuthenticationFactorType; + sms?: SmsResponse; + totp?: TotpWithSecretsResponse; user_id: string; } diff --git a/src/user-management/serializers/authentication-factor.serializer.ts b/src/user-management/serializers/authentication-factor.serializer.ts index 9c93d635c..ff740a07c 100644 --- a/src/user-management/serializers/authentication-factor.serializer.ts +++ b/src/user-management/serializers/authentication-factor.serializer.ts @@ -4,6 +4,7 @@ import { AuthenticationFactorWithSecrets, AuthenticationFactorWithSecretsResponse, } from '../interfaces/authentication-factor.interface'; +import { deserializeSms } from '../../multi-factor-auth/serializers/sms.serializer'; import { deserializeTotp, deserializeTotpWithSecrets, @@ -17,7 +18,8 @@ export const deserializeFactor = ( createdAt: factor.created_at, updatedAt: factor.updated_at, type: factor.type, - totp: deserializeTotp(factor.totp), + ...(factor.sms ? { sms: deserializeSms(factor.sms) } : {}), + ...(factor.totp ? { totp: deserializeTotp(factor.totp) } : {}), userId: factor.user_id, }); @@ -29,6 +31,7 @@ export const deserializeFactorWithSecrets = ( createdAt: factor.created_at, updatedAt: factor.updated_at, type: factor.type, - totp: deserializeTotpWithSecrets(factor.totp), + ...(factor.sms ? { sms: deserializeSms(factor.sms) } : {}), + ...(factor.totp ? { totp: deserializeTotpWithSecrets(factor.totp) } : {}), userId: factor.user_id, }); From 6cf80fd8dba889f9b8eb128ae25662ceddb3f8d8 Mon Sep 17 00:00:00 2001 From: "jeff.fiddler" Date: Fri, 24 Jul 2026 21:51:52 +0000 Subject: [PATCH 2/3] Add serializer spec covering user-management list-factors fixture Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../authentication-factor.serializer.spec.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/user-management/serializers/authentication-factor.serializer.spec.ts diff --git a/src/user-management/serializers/authentication-factor.serializer.spec.ts b/src/user-management/serializers/authentication-factor.serializer.spec.ts new file mode 100644 index 000000000..0b1a56afc --- /dev/null +++ b/src/user-management/serializers/authentication-factor.serializer.spec.ts @@ -0,0 +1,45 @@ +import { deserializeFactor } from './authentication-factor.serializer'; +import { AuthenticationFactorResponse } from '../interfaces/authentication-factor.interface'; +import listFactorsFixture from '../fixtures/list-factors.json'; + +describe('deserializeFactor', () => { + it('deserializes totp, sms, and generic_otp factors', () => { + const factors = listFactorsFixture.data.map((factor) => + deserializeFactor(factor as AuthenticationFactorResponse), + ); + + expect(factors).toEqual([ + { + object: 'authentication_factor', + id: 'auth_factor_1234', + createdAt: '2022-03-15T20:39:19.892Z', + updatedAt: '2022-03-15T20:39:19.892Z', + type: 'totp', + totp: { + issuer: 'WorkOS', + user: 'some_user', + }, + userId: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS', + }, + { + object: 'authentication_factor', + id: 'auth_factor_5678', + createdAt: '2022-03-15T20:39:19.892Z', + updatedAt: '2022-03-15T20:39:19.892Z', + type: 'sms', + sms: { + phoneNumber: '+15555555555', + }, + userId: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS', + }, + { + object: 'authentication_factor', + id: 'auth_factor_9012', + createdAt: '2022-03-15T20:39:19.892Z', + updatedAt: '2022-03-15T20:39:19.892Z', + type: 'generic_otp', + userId: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS', + }, + ]); + }); +}); From fc2d3da2f3bb36a77444d88b247586252deac274 Mon Sep 17 00:00:00 2001 From: "jeff.fiddler" Date: Fri, 24 Jul 2026 21:55:54 +0000 Subject: [PATCH 3/3] Narrow WithSecrets types to TOTP and reuse FactorType union Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../interfaces/factor.interface.ts | 2 +- .../interfaces/authentication-factor.interface.ts | 13 ++++++------- .../serializers/authentication-factor.serializer.ts | 3 +-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/multi-factor-auth/interfaces/factor.interface.ts b/src/multi-factor-auth/interfaces/factor.interface.ts index 98b31aa95..9d618fc84 100644 --- a/src/multi-factor-auth/interfaces/factor.interface.ts +++ b/src/multi-factor-auth/interfaces/factor.interface.ts @@ -6,7 +6,7 @@ import { TotpWithSecretsResponse, } from './totp.interface'; -type FactorType = 'sms' | 'totp' | 'generic_otp'; +export type FactorType = 'sms' | 'totp' | 'generic_otp'; export interface Factor { object: 'authentication_factor'; diff --git a/src/user-management/interfaces/authentication-factor.interface.ts b/src/user-management/interfaces/authentication-factor.interface.ts index 2a68da172..fa47744a6 100644 --- a/src/user-management/interfaces/authentication-factor.interface.ts +++ b/src/user-management/interfaces/authentication-factor.interface.ts @@ -1,3 +1,4 @@ +import { FactorType } from '../../multi-factor-auth/interfaces/factor.interface'; import { Sms, SmsResponse, @@ -9,7 +10,7 @@ import { TotpWithSecretsResponse, } from '../../multi-factor-auth/interfaces/totp.interface'; -export type AuthenticationFactorType = 'sms' | 'totp' | 'generic_otp'; +export type AuthenticationFactorType = FactorType; export interface AuthenticationFactor { object: 'authentication_factor'; @@ -27,9 +28,8 @@ export interface AuthenticationFactorWithSecrets { id: string; createdAt: string; updatedAt: string; - type: AuthenticationFactorType; - sms?: Sms; - totp?: TotpWithSecrets; + type: 'totp'; + totp: TotpWithSecrets; userId: string; } @@ -49,8 +49,7 @@ export interface AuthenticationFactorWithSecretsResponse { id: string; created_at: string; updated_at: string; - type: AuthenticationFactorType; - sms?: SmsResponse; - totp?: TotpWithSecretsResponse; + type: 'totp'; + totp: TotpWithSecretsResponse; user_id: string; } diff --git a/src/user-management/serializers/authentication-factor.serializer.ts b/src/user-management/serializers/authentication-factor.serializer.ts index ff740a07c..ce85ba3c0 100644 --- a/src/user-management/serializers/authentication-factor.serializer.ts +++ b/src/user-management/serializers/authentication-factor.serializer.ts @@ -31,7 +31,6 @@ export const deserializeFactorWithSecrets = ( createdAt: factor.created_at, updatedAt: factor.updated_at, type: factor.type, - ...(factor.sms ? { sms: deserializeSms(factor.sms) } : {}), - ...(factor.totp ? { totp: deserializeTotpWithSecrets(factor.totp) } : {}), + totp: deserializeTotpWithSecrets(factor.totp), userId: factor.user_id, });