verification-flow-polling.test.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. const source = fs.readFileSync('background/verification-flow.js', 'utf8');
  5. const globalScope = {};
  6. const api = new Function('self', `${source}; return self.MultiPageBackgroundVerificationFlow;`)(globalScope);
  7. test('verification flow extends 2925 polling window', () => {
  8. const helpers = api.createVerificationFlowHelpers({
  9. A4SKY_PROVIDER: 'a4sky',
  10. addLog: async () => {},
  11. chrome: { tabs: { update: async () => {} } },
  12. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  13. completeStepFromBackground: async () => {},
  14. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  15. getHotmailVerificationPollConfig: () => ({}),
  16. getHotmailVerificationRequestTimestamp: () => 0,
  17. getState: async () => ({}),
  18. getTabId: async () => 1,
  19. HOTMAIL_PROVIDER: 'hotmail-api',
  20. isStopError: () => false,
  21. LUCKMAIL_PROVIDER: 'luckmail-api',
  22. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  23. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  24. pollA4skyImapVerificationCode: async () => ({}),
  25. pollCloudflareTempEmailVerificationCode: async () => ({}),
  26. pollHotmailVerificationCode: async () => ({}),
  27. pollLuckmailVerificationCode: async () => ({}),
  28. sendToContentScript: async () => ({}),
  29. sendToMailContentScriptResilient: async () => ({}),
  30. setState: async () => {},
  31. setStepStatus: async () => {},
  32. sleepWithStop: async () => {},
  33. throwIfStopped: () => {},
  34. VERIFICATION_POLL_MAX_ROUNDS: 5,
  35. });
  36. const step4Payload = helpers.getVerificationPollPayload(4, { email: 'user@example.com', mailProvider: '2925' });
  37. const step8Payload = helpers.getVerificationPollPayload(8, { email: 'user@example.com', mailProvider: '2925' });
  38. assert.equal(step4Payload.maxAttempts, 15);
  39. assert.equal(step4Payload.intervalMs, 15000);
  40. assert.equal(step8Payload.maxAttempts, 15);
  41. assert.equal(step8Payload.intervalMs, 15000);
  42. });
  43. test('verification flow routes A4Sky provider to IMAP helper polling', async () => {
  44. let capturedPayload = null;
  45. const helpers = api.createVerificationFlowHelpers({
  46. A4SKY_PROVIDER: 'a4sky',
  47. addLog: async () => {},
  48. chrome: { tabs: { update: async () => {} } },
  49. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  50. completeStepFromBackground: async () => {},
  51. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  52. getHotmailVerificationPollConfig: () => ({}),
  53. getHotmailVerificationRequestTimestamp: () => 123,
  54. getState: async () => ({}),
  55. getTabId: async () => 1,
  56. HOTMAIL_PROVIDER: 'hotmail-api',
  57. isStopError: () => false,
  58. LUCKMAIL_PROVIDER: 'luckmail-api',
  59. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  60. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  61. pollA4skyImapVerificationCode: async (_step, _state, payload) => {
  62. capturedPayload = payload;
  63. return { code: '123456', emailTimestamp: 1 };
  64. },
  65. pollCloudflareTempEmailVerificationCode: async () => ({}),
  66. pollHotmailVerificationCode: async () => ({}),
  67. pollLuckmailVerificationCode: async () => ({}),
  68. sendToContentScript: async () => ({}),
  69. sendToMailContentScriptResilient: async () => ({}),
  70. setState: async () => {},
  71. setStepStatus: async () => {},
  72. sleepWithStop: async () => {},
  73. throwIfStopped: () => {},
  74. VERIFICATION_POLL_MAX_ROUNDS: 5,
  75. });
  76. const result = await helpers.pollFreshVerificationCode(4, {
  77. email: 'target@a4sky.com',
  78. mailProvider: 'a4sky',
  79. }, {
  80. provider: 'a4sky',
  81. label: 'A4Sky',
  82. }, {
  83. filterAfterTimestamp: 555,
  84. });
  85. assert.equal(result.code, '123456');
  86. assert.equal(capturedPayload.filterAfterTimestamp, 555);
  87. assert.equal(capturedPayload.targetEmail, 'target@a4sky.com');
  88. });
  89. test('verification flow runs beforeSubmit hook before filling the code', async () => {
  90. const events = [];
  91. const helpers = api.createVerificationFlowHelpers({
  92. addLog: async () => {},
  93. chrome: {
  94. tabs: {
  95. update: async () => {},
  96. },
  97. },
  98. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  99. completeStepFromBackground: async (_step, payload) => {
  100. events.push(['complete', payload.code]);
  101. },
  102. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  103. getHotmailVerificationPollConfig: () => ({}),
  104. getHotmailVerificationRequestTimestamp: () => 0,
  105. getState: async () => ({}),
  106. getTabId: async () => 1,
  107. HOTMAIL_PROVIDER: 'hotmail-api',
  108. isStopError: () => false,
  109. LUCKMAIL_PROVIDER: 'luckmail-api',
  110. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  111. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  112. pollCloudflareTempEmailVerificationCode: async () => ({}),
  113. pollHotmailVerificationCode: async () => ({}),
  114. pollLuckmailVerificationCode: async () => ({}),
  115. sendToContentScript: async (_source, message) => {
  116. if (message.type === 'FILL_CODE') {
  117. events.push(['submit', message.payload.code]);
  118. return {};
  119. }
  120. return {};
  121. },
  122. sendToMailContentScriptResilient: async () => ({
  123. code: '654321',
  124. emailTimestamp: 123,
  125. }),
  126. setState: async (payload) => {
  127. events.push(['state', payload.lastLoginCode || payload.lastSignupCode]);
  128. },
  129. setStepStatus: async () => {},
  130. sleepWithStop: async () => {},
  131. throwIfStopped: () => {},
  132. VERIFICATION_POLL_MAX_ROUNDS: 5,
  133. });
  134. await helpers.resolveVerificationStep(
  135. 7,
  136. { email: 'user@example.com', lastLoginCode: null },
  137. { provider: 'qq', label: 'QQ 邮箱' },
  138. {
  139. beforeSubmit: async (result) => {
  140. events.push(['beforeSubmit', result.code]);
  141. },
  142. }
  143. );
  144. assert.deepStrictEqual(events, [
  145. ['beforeSubmit', '654321'],
  146. ['submit', '654321'],
  147. ['state', '654321'],
  148. ['complete', '654321'],
  149. ]);
  150. });
  151. test('verification flow treats add-phone after login code submit as fatal instead of completing step 8', async () => {
  152. const events = [];
  153. const helpers = api.createVerificationFlowHelpers({
  154. addLog: async () => {},
  155. chrome: {
  156. tabs: {
  157. update: async () => {},
  158. },
  159. },
  160. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  161. completeStepFromBackground: async (_step, payload) => {
  162. events.push(['complete', payload.code]);
  163. },
  164. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  165. getHotmailVerificationPollConfig: () => ({}),
  166. getHotmailVerificationRequestTimestamp: () => 0,
  167. getState: async () => ({}),
  168. getTabId: async () => 1,
  169. HOTMAIL_PROVIDER: 'hotmail-api',
  170. isStopError: () => false,
  171. LUCKMAIL_PROVIDER: 'luckmail-api',
  172. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  173. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  174. pollCloudflareTempEmailVerificationCode: async () => ({}),
  175. pollHotmailVerificationCode: async () => ({}),
  176. pollLuckmailVerificationCode: async () => ({}),
  177. sendToContentScript: async (_source, message) => {
  178. if (message.type === 'FILL_CODE') {
  179. events.push(['submit', message.payload.code]);
  180. return {
  181. addPhonePage: true,
  182. url: 'https://auth.openai.com/add-phone',
  183. };
  184. }
  185. return {};
  186. },
  187. sendToMailContentScriptResilient: async () => ({
  188. code: '654321',
  189. emailTimestamp: 123,
  190. }),
  191. setState: async (payload) => {
  192. events.push(['state', payload.lastLoginCode || payload.lastSignupCode]);
  193. },
  194. setStepStatus: async () => {},
  195. sleepWithStop: async () => {},
  196. throwIfStopped: () => {},
  197. VERIFICATION_POLL_MAX_ROUNDS: 5,
  198. });
  199. await assert.rejects(
  200. () => helpers.resolveVerificationStep(
  201. 8,
  202. { email: 'user@example.com', lastLoginCode: null },
  203. { provider: 'qq', label: 'QQ 邮箱' },
  204. {}
  205. ),
  206. /验证码提交后页面进入手机号页面/
  207. );
  208. assert.deepStrictEqual(events, [
  209. ['submit', '654321'],
  210. ]);
  211. });
  212. test('verification flow caps mail polling timeout to the remaining oauth budget', async () => {
  213. const mailPollCalls = [];
  214. const helpers = api.createVerificationFlowHelpers({
  215. addLog: async () => {},
  216. chrome: {
  217. tabs: {
  218. update: async () => {},
  219. },
  220. },
  221. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  222. completeStepFromBackground: async () => {},
  223. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  224. getHotmailVerificationPollConfig: () => ({}),
  225. getHotmailVerificationRequestTimestamp: () => 0,
  226. getState: async () => ({}),
  227. getTabId: async () => 1,
  228. HOTMAIL_PROVIDER: 'hotmail-api',
  229. isStopError: () => false,
  230. LUCKMAIL_PROVIDER: 'luckmail-api',
  231. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  232. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  233. pollCloudflareTempEmailVerificationCode: async () => ({}),
  234. pollHotmailVerificationCode: async () => ({}),
  235. pollLuckmailVerificationCode: async () => ({}),
  236. sendToContentScript: async (_source, message) => {
  237. if (message.type === 'FILL_CODE') {
  238. return {};
  239. }
  240. return {};
  241. },
  242. sendToMailContentScriptResilient: async (_mail, message, options) => {
  243. mailPollCalls.push({
  244. payload: message.payload,
  245. options,
  246. });
  247. return { code: '654321', emailTimestamp: 123 };
  248. },
  249. setState: async () => {},
  250. setStepStatus: async () => {},
  251. sleepWithStop: async () => {},
  252. throwIfStopped: () => {},
  253. VERIFICATION_POLL_MAX_ROUNDS: 5,
  254. });
  255. await helpers.resolveVerificationStep(
  256. 8,
  257. {
  258. email: 'user@example.com',
  259. lastLoginCode: null,
  260. },
  261. { provider: 'qq', label: 'QQ 邮箱' },
  262. {
  263. getRemainingTimeMs: async () => 5000,
  264. resendIntervalMs: 0,
  265. }
  266. );
  267. assert.ok(mailPollCalls.length >= 1);
  268. assert.equal(mailPollCalls[0].options.timeoutMs, 5000);
  269. assert.equal(mailPollCalls[0].options.responseTimeoutMs, 5000);
  270. assert.equal(mailPollCalls[0].payload.maxAttempts, 2);
  271. });
  272. test('verification flow keeps Hotmail request timestamp filtering on the first poll', async () => {
  273. const pollPayloads = [];
  274. const helpers = api.createVerificationFlowHelpers({
  275. addLog: async () => {},
  276. chrome: { tabs: { update: async () => {} } },
  277. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  278. completeStepFromBackground: async () => {},
  279. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  280. getHotmailVerificationPollConfig: () => ({}),
  281. getHotmailVerificationRequestTimestamp: () => 87654,
  282. getState: async () => ({}),
  283. getTabId: async () => 1,
  284. HOTMAIL_PROVIDER: 'hotmail-api',
  285. isStopError: () => false,
  286. LUCKMAIL_PROVIDER: 'luckmail-api',
  287. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  288. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  289. pollCloudflareTempEmailVerificationCode: async () => ({}),
  290. pollHotmailVerificationCode: async (_step, _state, payload) => {
  291. pollPayloads.push(payload);
  292. return { code: '654321', emailTimestamp: 123 };
  293. },
  294. pollLuckmailVerificationCode: async () => ({}),
  295. sendToContentScript: async (_source, message) => {
  296. if (message.type === 'FILL_CODE') {
  297. return {};
  298. }
  299. return {};
  300. },
  301. sendToMailContentScriptResilient: async () => ({}),
  302. setState: async () => {},
  303. setStepStatus: async () => {},
  304. sleepWithStop: async () => {},
  305. throwIfStopped: () => {},
  306. VERIFICATION_POLL_MAX_ROUNDS: 5,
  307. });
  308. await helpers.resolveVerificationStep(
  309. 7,
  310. {
  311. email: 'user@example.com',
  312. loginVerificationRequestedAt: 100000,
  313. lastLoginCode: null,
  314. },
  315. { provider: 'hotmail-api', label: 'Hotmail' },
  316. {}
  317. );
  318. assert.equal(pollPayloads.length, 1);
  319. assert.equal(pollPayloads[0].filterAfterTimestamp, 87654);
  320. });
  321. test('verification flow keeps fixed filter timestamp after step 4 resend', async () => {
  322. const pollPayloads = [];
  323. let submitCount = 0;
  324. const helpers = api.createVerificationFlowHelpers({
  325. addLog: async () => {},
  326. chrome: { tabs: { update: async () => {} } },
  327. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  328. completeStepFromBackground: async () => {},
  329. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  330. getHotmailVerificationPollConfig: () => ({}),
  331. getHotmailVerificationRequestTimestamp: (_step, state) => Math.max(0, Number(state.signupVerificationRequestedAt || 0) - 15000),
  332. getState: async () => ({}),
  333. getTabId: async () => 1,
  334. HOTMAIL_PROVIDER: 'hotmail-api',
  335. isStopError: () => false,
  336. LUCKMAIL_PROVIDER: 'luckmail-api',
  337. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  338. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  339. pollCloudflareTempEmailVerificationCode: async () => ({}),
  340. pollHotmailVerificationCode: async (_step, _state, payload) => {
  341. pollPayloads.push(payload);
  342. return {
  343. code: pollPayloads.length === 1 ? '111111' : '222222',
  344. emailTimestamp: pollPayloads.length,
  345. };
  346. },
  347. pollLuckmailVerificationCode: async () => ({}),
  348. sendToContentScript: async (_source, message) => {
  349. if (message.type === 'FILL_CODE') {
  350. submitCount += 1;
  351. return submitCount === 1
  352. ? { invalidCode: true, errorText: '旧验证码' }
  353. : {};
  354. }
  355. if (message.type === 'RESEND_VERIFICATION_CODE') {
  356. return {};
  357. }
  358. return {};
  359. },
  360. sendToMailContentScriptResilient: async () => ({}),
  361. setState: async () => {},
  362. setStepStatus: async () => {},
  363. sleepWithStop: async () => {},
  364. throwIfStopped: () => {},
  365. VERIFICATION_POLL_MAX_ROUNDS: 5,
  366. });
  367. await helpers.resolveVerificationStep(
  368. 4,
  369. {
  370. email: 'user@example.com',
  371. signupVerificationRequestedAt: 100000,
  372. lastSignupCode: null,
  373. },
  374. { provider: 'hotmail-api', label: 'Hotmail' },
  375. {
  376. filterAfterTimestamp: 123456,
  377. }
  378. );
  379. assert.equal(pollPayloads.length, 2);
  380. assert.equal(pollPayloads[0].filterAfterTimestamp, 123456);
  381. assert.equal(pollPayloads[1].filterAfterTimestamp, 123456);
  382. });
  383. test('verification flow uses configured signup resend count for step 4', async () => {
  384. const resendSteps = [];
  385. let pollCalls = 0;
  386. const helpers = api.createVerificationFlowHelpers({
  387. addLog: async () => {},
  388. chrome: { tabs: { update: async () => {} } },
  389. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  390. completeStepFromBackground: async () => {},
  391. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  392. getHotmailVerificationPollConfig: () => ({}),
  393. getHotmailVerificationRequestTimestamp: () => 0,
  394. getState: async () => ({}),
  395. getTabId: async () => 1,
  396. HOTMAIL_PROVIDER: 'hotmail-api',
  397. isStopError: () => false,
  398. LUCKMAIL_PROVIDER: 'luckmail-api',
  399. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  400. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  401. pollCloudflareTempEmailVerificationCode: async () => ({}),
  402. pollHotmailVerificationCode: async () => ({}),
  403. pollLuckmailVerificationCode: async () => ({}),
  404. sendToContentScript: async (_source, message) => {
  405. if (message.type === 'RESEND_VERIFICATION_CODE') {
  406. resendSteps.push(message.step);
  407. }
  408. return {};
  409. },
  410. sendToMailContentScriptResilient: async () => {
  411. pollCalls += 1;
  412. return pollCalls === 2
  413. ? { code: '654321', emailTimestamp: 123 }
  414. : {};
  415. },
  416. setState: async () => {},
  417. setStepStatus: async () => {},
  418. sleepWithStop: async () => {},
  419. throwIfStopped: () => {},
  420. VERIFICATION_POLL_MAX_ROUNDS: 5,
  421. });
  422. await helpers.resolveVerificationStep(
  423. 4,
  424. {
  425. email: 'user@example.com',
  426. verificationResendCount: 2,
  427. lastSignupCode: null,
  428. },
  429. { provider: 'qq', label: 'QQ 邮箱' },
  430. {
  431. requestFreshCodeFirst: true,
  432. resendIntervalMs: 0,
  433. }
  434. );
  435. assert.deepStrictEqual(resendSteps, [4, 4]);
  436. assert.equal(pollCalls, 2);
  437. });
  438. test('verification flow uses configured login resend count for step 8', async () => {
  439. const resendSteps = [];
  440. let pollCalls = 0;
  441. const helpers = api.createVerificationFlowHelpers({
  442. addLog: async () => {},
  443. chrome: { tabs: { update: async () => {} } },
  444. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  445. completeStepFromBackground: async () => {},
  446. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  447. getHotmailVerificationPollConfig: () => ({}),
  448. getHotmailVerificationRequestTimestamp: () => 0,
  449. getState: async () => ({}),
  450. getTabId: async () => 1,
  451. HOTMAIL_PROVIDER: 'hotmail-api',
  452. isStopError: () => false,
  453. LUCKMAIL_PROVIDER: 'luckmail-api',
  454. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  455. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  456. pollCloudflareTempEmailVerificationCode: async () => ({}),
  457. pollHotmailVerificationCode: async () => ({}),
  458. pollLuckmailVerificationCode: async () => ({}),
  459. sendToContentScript: async (_source, message) => {
  460. if (message.type === 'RESEND_VERIFICATION_CODE') {
  461. resendSteps.push(message.step);
  462. }
  463. return {};
  464. },
  465. sendToMailContentScriptResilient: async () => {
  466. pollCalls += 1;
  467. return pollCalls === 3
  468. ? { code: '654321', emailTimestamp: 123 }
  469. : {};
  470. },
  471. setState: async () => {},
  472. setStepStatus: async () => {},
  473. sleepWithStop: async () => {},
  474. throwIfStopped: () => {},
  475. VERIFICATION_POLL_MAX_ROUNDS: 5,
  476. });
  477. await helpers.resolveVerificationStep(
  478. 8,
  479. {
  480. email: 'user@example.com',
  481. verificationResendCount: 2,
  482. lastLoginCode: null,
  483. },
  484. { provider: 'qq', label: 'QQ 邮箱' },
  485. {
  486. requestFreshCodeFirst: false,
  487. resendIntervalMs: 0,
  488. }
  489. );
  490. assert.deepStrictEqual(resendSteps, [8, 8]);
  491. assert.equal(pollCalls, 3);
  492. });