| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- /**
- * SMS polish proof (steps 1–5 of product polish).
- *
- * Checks, without requiring a real Twilio account:
- * 1) Config: SMS “ready” only when SID + token + From are all saved
- * 2) Methods: passwordlessSms on/off + ready = method AND secrets
- * 3) Honest delivery: no secrets → ok:false mode:log
- * 4) Honest delivery: bad Twilio → ok:false mode:error (mocked HTTP)
- * 5) Success path → ok:true mode:provider (mocked HTTP 201)
- * 6) Passwordless create+consume still works when SMS only logged
- * 7) Test helper rejects bad phone numbers
- *
- * Optional live Twilio (step 6 of the plan — only if YOU set env):
- * BRIVEN_SMS_LIVE_SID / BRIVEN_SMS_LIVE_TOKEN / BRIVEN_SMS_LIVE_FROM / BRIVEN_SMS_LIVE_TO
- * When all four are set, one real test SMS is sent (costs Twilio credit).
- *
- * Run:
- * cd apps/api
- * BRIVEN_ENGINE_DATABASE_URL=postgres://postgres:devpass@127.0.0.1:5434/briven_engine?sslmode=disable \
- * BRIVEN_DATA_PLANE_URL=postgres://postgres:devpass@127.0.0.1:5434/postgres?sslmode=disable \
- * BRIVEN_AUTH_CORE_ENABLED=true BRIVEN_ENV=development \
- * bun scripts/sms-polish-proof.mjs
- *
- * Human checklist after this script is green:
- * [ ] Dashboard → project → Providers → SMS card shows ready/not set
- * [ ] Security → SMS block matches Providers
- * [ ] Save Twilio secrets → SMS ready
- * [ ] Send test SMS to your phone (or live env above)
- * [ ] Deploy only when you say ship
- */
- process.env.BRIVEN_AUTH_CORE_ENABLED = 'true';
- process.env.BRIVEN_ENV = 'development';
- process.env.BRIVEN_ENGINE_DATABASE_URL =
- process.env.BRIVEN_ENGINE_DATABASE_URL ??
- 'postgres://postgres:devpass@127.0.0.1:5434/briven_engine?sslmode=disable';
- process.env.BRIVEN_DATA_PLANE_URL =
- process.env.BRIVEN_DATA_PLANE_URL ??
- 'postgres://postgres:devpass@127.0.0.1:5434/postgres?sslmode=disable';
- const { ensureBrivenEngineDatabase } = await import(
- '../src/services/auth-core/ensure-db.ts'
- );
- const { initAuthCoreSdk } = await import('../src/services/auth-core/engine.ts');
- const {
- getBrivenEngineProjectConfig,
- setBrivenEngineMethodFlags,
- setBrivenEngineSmsSecrets,
- } = await import('../src/services/auth-core/project-config.ts');
- const {
- createPasswordlessCode,
- consumePasswordlessCode,
- } = await import('../src/services/auth-core/passwordless.ts');
- const {
- sendBrivenEngineSms,
- sendBrivenEngineSmsTest,
- } = await import('../src/services/auth-core/delivery.ts');
- const projectId = `p_sms_polish_${Date.now().toString(36)}`;
- const phone = `+1555${String(Date.now()).slice(-7)}`;
- let failed = 0;
- function pass(label, detail) {
- console.log(` ✔ ${label}`, detail ?? '');
- }
- function fail(label, detail) {
- failed += 1;
- console.error(` ✘ ${label}`, detail ?? '');
- }
- function assert(cond, label, detail) {
- if (cond) pass(label, detail);
- else fail(label, detail);
- }
- /** Mock only api.twilio.com so proof works offline. */
- function withMockedTwilio(handler, run) {
- const realFetch = globalThis.fetch;
- globalThis.fetch = async (url, init) => {
- const u = String(url);
- if (u.includes('api.twilio.com')) {
- return handler(u, init);
- }
- return realFetch(url, init);
- };
- return Promise.resolve()
- .then(run)
- .finally(() => {
- globalThis.fetch = realFetch;
- });
- }
- console.log('=== SMS polish proof ===');
- console.log({ projectId, phone });
- const ensured = await ensureBrivenEngineDatabase();
- if (!ensured.ok) {
- console.error('FAIL ensure DB', ensured);
- console.error(
- 'Start local Doltgres (port 5434) then re-run. See compose.briven-engine.local.yml',
- );
- process.exit(1);
- }
- const inited = await initAuthCoreSdk();
- if (!inited) {
- console.error('FAIL init auth-core');
- process.exit(1);
- }
- // ── 1) Fresh project: SMS not configured ─────────────────────────────
- console.log('\n[1] config without secrets');
- {
- const cfg = await getBrivenEngineProjectConfig(projectId);
- assert(
- cfg.delivery.sms.configured === false,
- 'sms.configured is false',
- cfg.delivery.sms,
- );
- assert(
- cfg.delivery.sms.provider === null,
- 'sms.provider is null',
- cfg.delivery.sms.provider,
- );
- }
- // ── 2) Method on but no secrets → not ready ──────────────────────────
- console.log('\n[2] passwordlessSms method vs secrets');
- {
- await setBrivenEngineMethodFlags(projectId, { passwordlessSms: true });
- const cfg = await getBrivenEngineProjectConfig(projectId);
- assert(cfg.methods.passwordlessSms === true, 'method passwordlessSms on');
- assert(
- cfg.delivery.sms.configured === false,
- 'still not configured without secrets',
- );
- const chip = cfg.methodChips.find((c) => c.id === 'passwordless-sms');
- assert(chip?.enabled === true, 'chip enabled');
- assert(chip?.configured === false, 'chip configured=false (no Twilio)');
- const ready =
- cfg.methods.passwordlessSms && cfg.delivery.sms.configured;
- assert(ready === false, 'passwordlessSmsReady false');
- }
- // ── 3) Honest delivery without secrets ───────────────────────────────
- console.log('\n[3] delivery without secrets (honest fail)');
- {
- const sent = await sendBrivenEngineSms({
- phoneNumber: phone,
- userInputCode: '123456',
- projectId,
- type: 'PASSWORDLESS_LOGIN',
- });
- assert(sent.ok === false, 'ok=false without secrets', sent);
- assert(sent.mode === 'log', 'mode=log without secrets', sent.mode);
- assert(
- typeof sent.message === 'string' &&
- sent.message.toLowerCase().includes('sms not set'),
- 'message mentions SMS not set',
- sent.message,
- );
- const noProject = await sendBrivenEngineSms({
- phoneNumber: phone,
- userInputCode: '123456',
- type: 'PASSWORDLESS_LOGIN',
- });
- assert(noProject.ok === false, 'ok=false without projectId', noProject);
- assert(noProject.mode === 'log', 'mode=log without projectId', noProject.mode);
- }
- // ── 4) Passwordless create still works; delivery honest ──────────────
- console.log('\n[4] passwordless create+consume (delivery may be log)');
- {
- const created = await createPasswordlessCode({
- phoneNumber: phone,
- projectId,
- flowType: 'USER_INPUT_CODE',
- });
- assert(created.status === 'OK', 'create status OK', created);
- assert(
- created.status === 'OK' && created.channel === 'sms',
- 'channel=sms',
- created.status === 'OK' ? created.channel : null,
- );
- assert(
- created.status === 'OK' && created.delivery?.ok === false,
- 'delivery.ok false (no Twilio yet)',
- created.status === 'OK' ? created.delivery : null,
- );
- assert(
- created.status === 'OK' && Boolean(created.userInputCode),
- 'dev userInputCode present',
- );
- if (created.status === 'OK' && created.userInputCode) {
- const consumed = await consumePasswordlessCode({
- preAuthSessionId: created.preAuthSessionId,
- deviceId: created.deviceId,
- userInputCode: created.userInputCode,
- projectId,
- });
- assert(consumed.status === 'OK', 'consume OK even when SMS only logged', {
- status: consumed.status,
- user:
- consumed.status === 'OK'
- ? { id: consumed.user.id, phone: consumed.user.phone }
- : null,
- });
- }
- }
- // ── 5) Save secrets → configured ─────────────────────────────────────
- console.log('\n[5] save Twilio-compatible secrets');
- {
- await setBrivenEngineSmsSecrets(projectId, {
- accountSid: 'ACffffffffffffffffffffffffffffffff',
- authToken: 'test_auth_token_not_real',
- fromNumber: '+15550001111',
- });
- const cfg = await getBrivenEngineProjectConfig(projectId);
- assert(cfg.delivery.sms.configured === true, 'sms.configured true', cfg.delivery.sms);
- assert(
- cfg.delivery.sms.provider === 'twilio-compatible',
- 'provider twilio-compatible',
- );
- const chip = cfg.methodChips.find((c) => c.id === 'passwordless-sms');
- assert(chip?.configured === true, 'chip configured=true');
- const ready =
- cfg.methods.passwordlessSms && cfg.delivery.sms.configured;
- assert(ready === true, 'passwordlessSmsReady true');
- }
- // ── 6) Bad phone on test helper ──────────────────────────────────────
- console.log('\n[6] test helper rejects bad phone');
- {
- const bad = await sendBrivenEngineSmsTest({
- projectId,
- phoneNumber: '5551234',
- });
- assert(bad.ok === false, 'bad phone ok=false', bad);
- assert(bad.mode === 'error', 'bad phone mode=error', bad.mode);
- }
- // ── 7) Mock Twilio failure → honest error ────────────────────────────
- console.log('\n[7] mocked Twilio 401 → mode error');
- await withMockedTwilio(
- async () =>
- new Response(JSON.stringify({ message: 'Authenticate', code: 20003 }), {
- status: 401,
- headers: { 'content-type': 'application/json' },
- }),
- async () => {
- const sent = await sendBrivenEngineSmsTest({
- projectId,
- phoneNumber: phone,
- });
- assert(sent.ok === false, 'Twilio fail ok=false', sent);
- assert(sent.mode === 'error', 'Twilio fail mode=error', sent.mode);
- assert(
- typeof sent.message === 'string' && sent.message.includes('401'),
- 'message includes provider status',
- sent.message,
- );
- },
- );
- // ── 8) Mock Twilio success → provider ────────────────────────────────
- console.log('\n[8] mocked Twilio 201 → mode provider');
- await withMockedTwilio(
- async () =>
- new Response(JSON.stringify({ sid: 'SM_mock_success', status: 'queued' }), {
- status: 201,
- headers: { 'content-type': 'application/json' },
- }),
- async () => {
- const sent = await sendBrivenEngineSmsTest({
- projectId,
- phoneNumber: phone,
- });
- assert(sent.ok === true, 'Twilio success ok=true', sent);
- assert(sent.mode === 'provider', 'Twilio success mode=provider', sent.mode);
- },
- );
- // ── 9) Method off with secrets → ready false ─────────────────────────
- console.log('\n[9] method off → ready false even with secrets');
- {
- await setBrivenEngineMethodFlags(projectId, { passwordlessSms: false });
- const cfg = await getBrivenEngineProjectConfig(projectId);
- assert(cfg.delivery.sms.configured === true, 'secrets still saved');
- assert(cfg.methods.passwordlessSms === false, 'method off');
- const ready =
- cfg.methods.passwordlessSms && cfg.delivery.sms.configured;
- assert(ready === false, 'ready false when method off');
- // restore for any follow-on
- await setBrivenEngineMethodFlags(projectId, { passwordlessSms: true });
- }
- // ── 10) Optional real Twilio (only with env) ─────────────────────────
- console.log('\n[10] optional live Twilio');
- const liveSid = process.env.BRIVEN_SMS_LIVE_SID?.trim();
- const liveToken = process.env.BRIVEN_SMS_LIVE_TOKEN?.trim();
- const liveFrom = process.env.BRIVEN_SMS_LIVE_FROM?.trim();
- const liveTo = process.env.BRIVEN_SMS_LIVE_TO?.trim();
- if (liveSid && liveToken && liveFrom && liveTo) {
- const liveProject = `p_sms_live_${Date.now().toString(36)}`;
- await setBrivenEngineSmsSecrets(liveProject, {
- accountSid: liveSid,
- authToken: liveToken,
- fromNumber: liveFrom,
- });
- const live = await sendBrivenEngineSmsTest({
- projectId: liveProject,
- phoneNumber: liveTo,
- });
- assert(live.ok === true, 'LIVE test SMS sent', live);
- assert(live.mode === 'provider', 'LIVE mode=provider', live.mode);
- console.log(' (check phone', liveTo, 'for Briven Auth test message)');
- } else {
- console.log(
- ' · skipped (set BRIVEN_SMS_LIVE_SID, _TOKEN, _FROM, _TO to send a real text)',
- );
- }
- // ── Summary ──────────────────────────────────────────────────────────
- console.log('');
- if (failed > 0) {
- console.error(`✘ SMS POLISH PROOF FAILED (${failed} assertion(s))`);
- process.exit(1);
- }
- console.log('✔ SMS POLISH PROOF OK');
- console.log(' config ready only with SID+token+From');
- console.log(' method + secrets → ready; either missing → not ready');
- console.log(' no secrets → delivery ok=false mode=log');
- console.log(' Twilio fail → delivery ok=false mode=error');
- console.log(' Twilio ok (mocked) → delivery ok=true mode=provider');
- console.log(' passwordless create+consume works without real SMS');
- console.log(' test helper rejects non-E.164 phones');
- console.log('');
- console.log('Next: dashboard smoke (Providers + Security + test button),');
- console.log('then live Twilio when you say go, then deploy only with your OK.');
- process.exit(0);
|