Skip to content

Commit db71e40

Browse files
committed
fix(oauth): Prevent validation errors from orphaned client tokens
Because: * Sentry showed ValidationError: "[0].name" is required * OAuth token queries use LEFT OUTER JOIN with clients table * When a client is deleted but tokens remain (orphaned), the JOIN returns NULL * This may be converted to undefined, which fails Joi validation This commit: * Add nullish coalescing in factories.ts when merging OAuth client names; Joi validation explicitely allows null * Fix shared reference bug in getDefaultClientFields() to return copy of defaults * Add regression test for undefined client_name handling Closes #FXA-13132
1 parent 54e375e commit db71e40

2 files changed

Lines changed: 20 additions & 2 deletions

File tree

packages/fxa-shared/connected-services/factories.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export class ConnectedServicesFactory {
257257
// We fill in a default device name from the OAuth client name,
258258
// but individual clients can override this in their device record registration.
259259
if (!client.name) {
260-
client.name = oauthClient.client_name;
260+
client.name = oauthClient.client_name ?? null;
261261
}
262262
// For now we assume that all oauth clients that register a device record are mobile apps.
263263
// Ref https://github.com/mozilla/fxa/issues/449
@@ -292,6 +292,6 @@ export class ConnectedServicesFactory {
292292
}
293293

294294
protected getDefaultClientFields(): AttachedClient {
295-
return attachedClientsDefaults;
295+
return { ...attachedClientsDefaults };
296296
}
297297
}

packages/fxa-shared/test/connected-services/factories.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,5 +193,23 @@ describe('connected-services/factories', () => {
193193
Sinon.assert.calledOnce(bStubbed.oauthClients);
194194
Sinon.assert.calledOnce(bStubbed.sessions);
195195
});
196+
197+
it('handles undefined client_name without validation errors', async () => {
198+
oauthClients = [
199+
{
200+
refresh_token_id: 'test-oauth',
201+
created_time: Date.now(),
202+
last_access_time: Date.now(),
203+
client_name: undefined as any, // Simulate undefined from database
204+
} as AttachedOAuthClient,
205+
];
206+
deviceList = [];
207+
sessions = [];
208+
209+
const results = await factory.build('1234', 'en');
210+
211+
// Verify name is null, not undefined (required for validation)
212+
assert.strictEqual(results[0].name, null);
213+
});
196214
});
197215
});

0 commit comments

Comments
 (0)