verification-flow-polling.test.js 16 KB

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