step2-passwordless-proof.mjs189 lines · main
1/**
2 * Step 2 proof: magic-link email + SMS OTP on Doltgres.
3 *
4 * cd apps/api
5 * BRIVEN_ENGINE_DATABASE_URL=postgres://postgres:devpass@127.0.0.1:5434/briven_engine?sslmode=disable \
6 * BRIVEN_DATA_PLANE_URL=postgres://postgres:devpass@127.0.0.1:5434/postgres?sslmode=disable \
7 * BRIVEN_AUTH_CORE_ENABLED=true BRIVEN_ENV=development \
8 * bun scripts/step2-passwordless-proof.mjs
9 */
10
11process.env.BRIVEN_AUTH_CORE_ENABLED = 'true';
12process.env.BRIVEN_ENV = 'development';
13process.env.BRIVEN_ENGINE_DATABASE_URL =
14 process.env.BRIVEN_ENGINE_DATABASE_URL ??
15 'postgres://postgres:devpass@127.0.0.1:5434/briven_engine?sslmode=disable';
16process.env.BRIVEN_DATA_PLANE_URL =
17 process.env.BRIVEN_DATA_PLANE_URL ??
18 'postgres://postgres:devpass@127.0.0.1:5434/postgres?sslmode=disable';
19
20const { ensureBrivenEngineDatabase } = await import(
21 '../src/services/auth-core/ensure-db.ts'
22);
23const { initAuthCoreSdk } = await import('../src/services/auth-core/engine.ts');
24const {
25 createPasswordlessCode,
26 consumePasswordlessCode,
27} = await import('../src/services/auth-core/passwordless.ts');
28const { getEnginePool } = await import('../src/services/auth-core/db.ts');
29
30const projectId = 'p_step2_local';
31const email = `step2_${Date.now()}@example.com`;
32const phone = `+1555${String(Date.now()).slice(-7)}`;
33
34console.log('=== Phase 3: passwordless (email magic + SMS OTP) on Doltgres ===');
35console.log({ email, phone, projectId });
36
37const ensured = await ensureBrivenEngineDatabase();
38if (!ensured.ok) {
39 console.error('FAIL ensure', ensured);
40 process.exit(1);
41}
42const inited = await initAuthCoreSdk();
43if (!inited) {
44 console.error('FAIL init');
45 process.exit(1);
46}
47
48// ── Email: OTP + magic link ─────────────────────────────────────────
49const emailCreate = await createPasswordlessCode({
50 email,
51 projectId,
52 flowType: 'USER_INPUT_CODE_AND_MAGIC_LINK',
53});
54console.log('email create', {
55 status: emailCreate.status,
56 channel: emailCreate.status === 'OK' ? emailCreate.channel : null,
57 hasCode: emailCreate.status === 'OK' ? Boolean(emailCreate.userInputCode) : false,
58 hasLink: emailCreate.status === 'OK' ? Boolean(emailCreate.linkCode) : false,
59 delivery: emailCreate.status === 'OK' ? emailCreate.delivery : null,
60});
61if (emailCreate.status !== 'OK' || !emailCreate.userInputCode) {
62 console.error('FAIL email create', emailCreate);
63 process.exit(1);
64}
65
66// Wrong code
67const bad = await consumePasswordlessCode({
68 preAuthSessionId: emailCreate.preAuthSessionId,
69 deviceId: emailCreate.deviceId,
70 userInputCode: '000000',
71 projectId,
72});
73console.log('email wrong OTP', bad.status);
74if (bad.status === 'OK') {
75 console.error('FAIL accepted wrong OTP');
76 process.exit(1);
77}
78
79// Correct OTP
80const emailConsume = await consumePasswordlessCode({
81 preAuthSessionId: emailCreate.preAuthSessionId,
82 deviceId: emailCreate.deviceId,
83 userInputCode: emailCreate.userInputCode,
84 projectId,
85});
86console.log('email consume OTP', {
87 status: emailConsume.status,
88 userId: emailConsume.status === 'OK' ? emailConsume.user.id : null,
89 createdNewUser:
90 emailConsume.status === 'OK' ? emailConsume.createdNewUser : null,
91 session:
92 emailConsume.status === 'OK' ? emailConsume.session.handle : null,
93});
94if (emailConsume.status !== 'OK') {
95 console.error('FAIL email consume', emailConsume);
96 process.exit(1);
97}
98
99// Magic link path (fresh code)
100const emailCreate2 = await createPasswordlessCode({
101 email: `step2link_${Date.now()}@example.com`,
102 projectId,
103 flowType: 'MAGIC_LINK',
104});
105if (emailCreate2.status !== 'OK' || !emailCreate2.linkCode) {
106 console.error('FAIL magic create', emailCreate2);
107 process.exit(1);
108}
109const linkConsume = await consumePasswordlessCode({
110 preAuthSessionId: emailCreate2.preAuthSessionId,
111 deviceId: emailCreate2.deviceId,
112 linkCode: emailCreate2.linkCode,
113 projectId,
114});
115console.log('email magic link consume', {
116 status: linkConsume.status,
117 userId: linkConsume.status === 'OK' ? linkConsume.user.id : null,
118});
119if (linkConsume.status !== 'OK') {
120 console.error('FAIL magic consume', linkConsume);
121 process.exit(1);
122}
123
124// ── SMS OTP ─────────────────────────────────────────────────────────
125const smsCreate = await createPasswordlessCode({
126 phoneNumber: phone,
127 projectId,
128 flowType: 'USER_INPUT_CODE',
129});
130console.log('sms create', {
131 status: smsCreate.status,
132 channel: smsCreate.status === 'OK' ? smsCreate.channel : null,
133 hasCode: smsCreate.status === 'OK' ? Boolean(smsCreate.userInputCode) : false,
134 delivery: smsCreate.status === 'OK' ? smsCreate.delivery : null,
135});
136if (smsCreate.status !== 'OK' || !smsCreate.userInputCode) {
137 console.error('FAIL sms create', smsCreate);
138 process.exit(1);
139}
140
141const smsConsume = await consumePasswordlessCode({
142 preAuthSessionId: smsCreate.preAuthSessionId,
143 deviceId: smsCreate.deviceId,
144 userInputCode: smsCreate.userInputCode,
145 projectId,
146});
147console.log('sms consume', {
148 status: smsConsume.status,
149 userId: smsConsume.status === 'OK' ? smsConsume.user.id : null,
150 phone: smsConsume.status === 'OK' ? smsConsume.user.phone : null,
151 session: smsConsume.status === 'OK' ? smsConsume.session.handle : null,
152});
153if (smsConsume.status !== 'OK') {
154 console.error('FAIL sms consume', smsConsume);
155 process.exit(1);
156}
157
158// SQL proof
159const pool = getEnginePool();
160const users = await pool.query(
161 `SELECT id, email, phone, tenant_id FROM be_users WHERE tenant_id LIKE 'proj-p-step2%' ORDER BY time_joined DESC LIMIT 10`,
162);
163const sessions = await pool.query(
164 `SELECT session_handle, user_id FROM be_sessions WHERE user_id = ANY($1::text[])`,
165 [
166 [
167 emailConsume.user.id,
168 linkConsume.user.id,
169 smsConsume.user.id,
170 ],
171 ],
172);
173const leftoverCodes = await pool.query(
174 `SELECT COUNT(*)::int AS n FROM be_passwordless_codes`,
175);
176
177console.log('SQL users', users.rows);
178console.log('SQL sessions for step2 users', sessions.rowCount);
179console.log('leftover codes (should be 0 used ones gone)', leftoverCodes.rows[0]);
180
181console.log('');
182console.log('✔ PHASE 3 LOCAL PROOF OK (passwordless)');
183console.log(' storage: Doltgres');
184console.log(' email OTP: OK');
185console.log(' email magic link: OK');
186console.log(' SMS OTP: OK (delivery mode log/provider)');
187console.log(' wrong OTP rejected: OK');
188console.log(' sessions created: OK (handle = cookie)');
189process.exit(0);