Skip to content

Commit 0752ea7

Browse files
authored
feat: remove stych, add prelude client (#3312)
1 parent 9846108 commit 0752ea7

12 files changed

Lines changed: 138 additions & 157 deletions

File tree

package-lock.json

Lines changed: 31 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/backend/clients/event/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,8 @@ export type EventMap = {
137137
* the request for log / support correlation.
138138
*/
139139
trail_id?: string;
140-
/** Device signals forwarded verbatim from the signup request body. */
140+
/** Device signal forwarded verbatim from the signup request body. */
141141
fingerprint?: string | null;
142-
dfp_telemetry_id?: string | null;
143142
/** Set by the abuse harness — require SMS phone verification post-signup. */
144143
requires_phone_verification?: boolean;
145144
/** Set by the abuse harness — require card verification post-signup. */

src/backend/clients/prelude/PreludeClient.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,32 @@ describe('PreludeClient', () => {
115115
);
116116
});
117117

118+
it('forwards dispatch_id as a top-level field, not inside signals', async () => {
119+
fetchMock.mockResolvedValue(okJson({ id: 'v', status: 'success' }));
120+
const client = makeClient('sk_test');
121+
122+
await client.createVerification('+14155550123', {
123+
ip: '203.0.113.7',
124+
dispatch_id: 'd1f5e9a0-0000-4000-8000-000000000000',
125+
});
126+
127+
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
128+
expect(body.dispatch_id).toBe('d1f5e9a0-0000-4000-8000-000000000000');
129+
expect(body.signals).toEqual({ ip: '203.0.113.7' });
130+
expect(body.signals).not.toHaveProperty('dispatch_id');
131+
});
132+
133+
it('omits dispatch_id when not supplied', async () => {
134+
fetchMock.mockResolvedValue(okJson({ id: 'v', status: 'success' }));
135+
const client = makeClient('sk_test');
136+
137+
await client.createVerification('+14155550123', { ip: '203.0.113.7' });
138+
139+
expect(JSON.parse(fetchMock.mock.calls[0][1].body)).not.toHaveProperty(
140+
'dispatch_id',
141+
);
142+
});
143+
118144
it('includes a configured template_id + sender_id + preferred channel', async () => {
119145
fetchMock.mockResolvedValue(okJson({ id: 'v', status: 'success' }));
120146
const client = new PreludeClient({

src/backend/clients/prelude/PreludeClient.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,17 @@ export class PreludeClient extends PuterClient {
125125
* device + platform are the highest-value). We pass what the backend
126126
* already has: the request `ip`, the client `device_id` (ThumbmarkJS
127127
* hash), and the `user_agent` (Prelude infers platform/model/OS from it).
128-
* Richer client-collected signals require Prelude's frontend SDK, which
129-
* produces a `dispatch_id` the backend would forward here.
128+
* `dispatch_id` carries the richer browser signals gathered by Prelude's
129+
* frontend JS Signals SDK; it's a top-level field in the request, not part
130+
* of the `signals` object, so it's pulled out below.
130131
*/
131132
async createVerification(
132133
target: string,
133134
signals: {
134135
ip?: string;
135136
device_id?: string;
136137
user_agent?: string;
138+
dispatch_id?: string;
137139
} = {},
138140
): Promise<{ id?: string; status: PreludeCreateStatus }> {
139141
// Match the 6-box code UI (UIWindowPhoneVerificationRequired). Without
@@ -164,6 +166,8 @@ export class PreludeClient extends PuterClient {
164166
if (signals.device_id) sig.device_id = signals.device_id;
165167
if (signals.user_agent) sig.user_agent = signals.user_agent;
166168
if (Object.keys(sig).length > 0) body.signals = sig;
169+
// dispatch_id is a top-level field, not a member of `signals`.
170+
if (signals.dispatch_id) body.dispatch_id = signals.dispatch_id;
167171
return this.#post('/verification', body) as Promise<{
168172
id?: string;
169173
status: PreludeCreateStatus;

src/backend/controllers/auth/AuthController.test.ts

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ describe('AuthController.handleSignup', () => {
609609
});
610610
});
611611

612-
// -- Signup device signals (fingerprint + dfp_telemetry_id) --
612+
// -- Signup device signal (fingerprint) --
613613

614614
describe('AuthController.handleSignup device signals', () => {
615615
const uniq = () => Math.random().toString(36).slice(2, 10);
@@ -631,11 +631,10 @@ describe('AuthController.handleSignup device signals', () => {
631631
(evt) => (evt as { username?: string }).username === username,
632632
);
633633

634-
it('forwards fingerprint and dfp_telemetry_id verbatim to validate and success events', async () => {
634+
it('forwards fingerprint verbatim to validate and success events', async () => {
635635
const username = `fp_${uniq()}`;
636636
const baseline = heardSignupSuccess.length;
637637
const fingerprint = 'Fp_abc.123-XYZ';
638-
const dfpTelemetryId = 'tel_id-456';
639638

640639
const seen = await captureValidateEvents(async () => {
641640
const res = makeRes();
@@ -645,7 +644,6 @@ describe('AuthController.handleSignup device signals', () => {
645644
email: `${username}@test.local`,
646645
password: 'correct-horse-battery',
647646
fingerprint,
648-
dfp_telemetry_id: dfpTelemetryId,
649647
}),
650648
res,
651649
);
@@ -654,15 +652,14 @@ describe('AuthController.handleSignup device signals', () => {
654652

655653
expect(seen).toHaveLength(1);
656654
expect(seen[0].fingerprint).toBe(fingerprint);
657-
expect(seen[0].dfp_telemetry_id).toBe(dfpTelemetryId);
658655

659656
const successes = successEventsFor(baseline, username);
660657
expect(successes).toHaveLength(1);
661658
expect(successes[0].fingerprint).toBe(fingerprint);
662659
expect(successes[0].is_temp).toBe(false);
663660
});
664661

665-
it('accepts boundary-length values (128-char fingerprint, 64-char dfp_telemetry_id)', async () => {
662+
it('accepts a boundary-length 128-char fingerprint', async () => {
666663
const username = `fp_${uniq()}`;
667664
const res = makeRes();
668665
await controller.handleSignup(
@@ -671,14 +668,13 @@ describe('AuthController.handleSignup device signals', () => {
671668
email: `${username}@test.local`,
672669
password: 'correct-horse-battery',
673670
fingerprint: 'f'.repeat(128),
674-
dfp_telemetry_id: 'd'.repeat(64),
675671
}),
676672
res,
677673
);
678674
expect(isCompleteLoginResponse(res.body)).toBe(true);
679675
});
680676

681-
it('defaults both fields to null on the validate event when absent', async () => {
677+
it('defaults the fingerprint to null on the validate event when absent', async () => {
682678
const username = `fp_${uniq()}`;
683679
const baseline = heardSignupSuccess.length;
684680

@@ -699,15 +695,14 @@ describe('AuthController.handleSignup device signals', () => {
699695

700696
expect(seen).toHaveLength(1);
701697
expect(seen[0].fingerprint).toBeNull();
702-
expect(seen[0].dfp_telemetry_id).toBeNull();
703698

704699
const successes = successEventsFor(baseline, username);
705700
expect(successes).toHaveLength(1);
706701
expect(successes[0].fingerprint).toBeNull();
707702
expect(successes[0].is_temp).toBe(false);
708703
});
709704

710-
it('treats empty-string fingerprint and dfp_telemetry_id as absent', async () => {
705+
it('treats an empty-string fingerprint as absent', async () => {
711706
const username = `fp_${uniq()}`;
712707

713708
const seen = await captureValidateEvents(async () => {
@@ -718,7 +713,6 @@ describe('AuthController.handleSignup device signals', () => {
718713
email: `${username}@test.local`,
719714
password: 'correct-horse-battery',
720715
fingerprint: '',
721-
dfp_telemetry_id: '',
722716
}),
723717
res,
724718
);
@@ -728,7 +722,6 @@ describe('AuthController.handleSignup device signals', () => {
728722

729723
expect(seen).toHaveLength(1);
730724
expect(seen[0].fingerprint).toBeNull();
731-
expect(seen[0].dfp_telemetry_id).toBeNull();
732725
});
733726

734727
it('rejects a non-string fingerprint with 400 and fires no success event', async () => {
@@ -770,37 +763,6 @@ describe('AuthController.handleSignup device signals', () => {
770763
expect(heardSignupSuccess.length).toBe(baseline);
771764
});
772765

773-
it('rejects a dfp_telemetry_id longer than 64 characters with 400 and fires no success event', async () => {
774-
const username = `fp_${uniq()}`;
775-
const baseline = heardSignupSuccess.length;
776-
await expect(
777-
controller.handleSignup(
778-
makeReq({
779-
username,
780-
email: `${username}@test.local`,
781-
password: 'correct-horse-battery',
782-
dfp_telemetry_id: 'd'.repeat(65),
783-
}),
784-
makeRes(),
785-
),
786-
).rejects.toMatchObject({ statusCode: 400 });
787-
expect(heardSignupSuccess.length).toBe(baseline);
788-
});
789-
790-
it('rejects a non-string dfp_telemetry_id with 400', async () => {
791-
await expect(
792-
controller.handleSignup(
793-
makeReq({
794-
username: `fp_${uniq()}`,
795-
email: `${uniq()}@test.local`,
796-
password: 'correct-horse-battery',
797-
dfp_telemetry_id: { nested: true },
798-
}),
799-
makeRes(),
800-
),
801-
).rejects.toMatchObject({ statusCode: 400 });
802-
});
803-
804766
it('temp-user signup reports is_temp true and carries the fingerprint on the success event', async () => {
805767
const baseline = heardSignupSuccess.length;
806768
const res = makeRes();

0 commit comments

Comments
 (0)