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 | |
| 11 | process.env.BRIVEN_AUTH_CORE_ENABLED = 'true'; |
| 12 | process.env.BRIVEN_ENV = 'development'; |
| 13 | process.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'; |
| 16 | process.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 | |
| 20 | const { ensureBrivenEngineDatabase } = await import( |
| 21 | '../src/services/auth-core/ensure-db.ts' |
| 22 | ); |
| 23 | const { initAuthCoreSdk } = await import('../src/services/auth-core/engine.ts'); |
| 24 | const { |
| 25 | createPasswordlessCode, |
| 26 | consumePasswordlessCode, |
| 27 | } = await import('../src/services/auth-core/passwordless.ts'); |
| 28 | const { getEnginePool } = await import('../src/services/auth-core/db.ts'); |
| 29 | |
| 30 | const projectId = 'p_step2_local'; |
| 31 | const email = `step2_${Date.now()}@example.com`; |
| 32 | const phone = `+1555${String(Date.now()).slice(-7)}`; |
| 33 | |
| 34 | console.log('=== Phase 3: passwordless (email magic + SMS OTP) on Doltgres ==='); |
| 35 | console.log({ email, phone, projectId }); |
| 36 | |
| 37 | const ensured = await ensureBrivenEngineDatabase(); |
| 38 | if (!ensured.ok) { |
| 39 | console.error('FAIL ensure', ensured); |
| 40 | process.exit(1); |
| 41 | } |
| 42 | const inited = await initAuthCoreSdk(); |
| 43 | if (!inited) { |
| 44 | console.error('FAIL init'); |
| 45 | process.exit(1); |
| 46 | } |
| 47 | |
| 48 | // ── Email: OTP + magic link ───────────────────────────────────────── |
| 49 | const emailCreate = await createPasswordlessCode({ |
| 50 | email, |
| 51 | projectId, |
| 52 | flowType: 'USER_INPUT_CODE_AND_MAGIC_LINK', |
| 53 | }); |
| 54 | console.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 | }); |
| 61 | if (emailCreate.status !== 'OK' || !emailCreate.userInputCode) { |
| 62 | console.error('FAIL email create', emailCreate); |
| 63 | process.exit(1); |
| 64 | } |
| 65 | |
| 66 | // Wrong code |
| 67 | const bad = await consumePasswordlessCode({ |
| 68 | preAuthSessionId: emailCreate.preAuthSessionId, |
| 69 | deviceId: emailCreate.deviceId, |
| 70 | userInputCode: '000000', |
| 71 | projectId, |
| 72 | }); |
| 73 | console.log('email wrong OTP', bad.status); |
| 74 | if (bad.status === 'OK') { |
| 75 | console.error('FAIL accepted wrong OTP'); |
| 76 | process.exit(1); |
| 77 | } |
| 78 | |
| 79 | // Correct OTP |
| 80 | const emailConsume = await consumePasswordlessCode({ |
| 81 | preAuthSessionId: emailCreate.preAuthSessionId, |
| 82 | deviceId: emailCreate.deviceId, |
| 83 | userInputCode: emailCreate.userInputCode, |
| 84 | projectId, |
| 85 | }); |
| 86 | console.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 | }); |
| 94 | if (emailConsume.status !== 'OK') { |
| 95 | console.error('FAIL email consume', emailConsume); |
| 96 | process.exit(1); |
| 97 | } |
| 98 | |
| 99 | // Magic link path (fresh code) |
| 100 | const emailCreate2 = await createPasswordlessCode({ |
| 101 | email: `step2link_${Date.now()}@example.com`, |
| 102 | projectId, |
| 103 | flowType: 'MAGIC_LINK', |
| 104 | }); |
| 105 | if (emailCreate2.status !== 'OK' || !emailCreate2.linkCode) { |
| 106 | console.error('FAIL magic create', emailCreate2); |
| 107 | process.exit(1); |
| 108 | } |
| 109 | const linkConsume = await consumePasswordlessCode({ |
| 110 | preAuthSessionId: emailCreate2.preAuthSessionId, |
| 111 | deviceId: emailCreate2.deviceId, |
| 112 | linkCode: emailCreate2.linkCode, |
| 113 | projectId, |
| 114 | }); |
| 115 | console.log('email magic link consume', { |
| 116 | status: linkConsume.status, |
| 117 | userId: linkConsume.status === 'OK' ? linkConsume.user.id : null, |
| 118 | }); |
| 119 | if (linkConsume.status !== 'OK') { |
| 120 | console.error('FAIL magic consume', linkConsume); |
| 121 | process.exit(1); |
| 122 | } |
| 123 | |
| 124 | // ── SMS OTP ───────────────────────────────────────────────────────── |
| 125 | const smsCreate = await createPasswordlessCode({ |
| 126 | phoneNumber: phone, |
| 127 | projectId, |
| 128 | flowType: 'USER_INPUT_CODE', |
| 129 | }); |
| 130 | console.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 | }); |
| 136 | if (smsCreate.status !== 'OK' || !smsCreate.userInputCode) { |
| 137 | console.error('FAIL sms create', smsCreate); |
| 138 | process.exit(1); |
| 139 | } |
| 140 | |
| 141 | const smsConsume = await consumePasswordlessCode({ |
| 142 | preAuthSessionId: smsCreate.preAuthSessionId, |
| 143 | deviceId: smsCreate.deviceId, |
| 144 | userInputCode: smsCreate.userInputCode, |
| 145 | projectId, |
| 146 | }); |
| 147 | console.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 | }); |
| 153 | if (smsConsume.status !== 'OK') { |
| 154 | console.error('FAIL sms consume', smsConsume); |
| 155 | process.exit(1); |
| 156 | } |
| 157 | |
| 158 | // SQL proof |
| 159 | const pool = getEnginePool(); |
| 160 | const 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 | ); |
| 163 | const 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 | ); |
| 173 | const leftoverCodes = await pool.query( |
| 174 | `SELECT COUNT(*)::int AS n FROM be_passwordless_codes`, |
| 175 | ); |
| 176 | |
| 177 | console.log('SQL users', users.rows); |
| 178 | console.log('SQL sessions for step2 users', sessions.rowCount); |
| 179 | console.log('leftover codes (should be 0 used ones gone)', leftoverCodes.rows[0]); |
| 180 | |
| 181 | console.log(''); |
| 182 | console.log('✔ PHASE 3 LOCAL PROOF OK (passwordless)'); |
| 183 | console.log(' storage: Doltgres'); |
| 184 | console.log(' email OTP: OK'); |
| 185 | console.log(' email magic link: OK'); |
| 186 | console.log(' SMS OTP: OK (delivery mode log/provider)'); |
| 187 | console.log(' wrong OTP rejected: OK'); |
| 188 | console.log(' sessions created: OK (handle = cookie)'); |
| 189 | process.exit(0); |