step2-passwordless-proof.mjs 6.4 KB

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