verification-flow-polling.test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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 caps mail polling timeout to the remaining oauth budget', async () => {
  104. const mailPollCalls = [];
  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 () => {},
  114. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  115. getHotmailVerificationPollConfig: () => ({}),
  116. getHotmailVerificationRequestTimestamp: () => 0,
  117. getState: async () => ({}),
  118. getTabId: async () => 1,
  119. HOTMAIL_PROVIDER: 'hotmail-api',
  120. isStopError: () => false,
  121. LUCKMAIL_PROVIDER: 'luckmail-api',
  122. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  123. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  124. pollCloudflareTempEmailVerificationCode: async () => ({}),
  125. pollHotmailVerificationCode: async () => ({}),
  126. pollLuckmailVerificationCode: async () => ({}),
  127. sendToContentScript: async (_source, message) => {
  128. if (message.type === 'FILL_CODE') {
  129. return {};
  130. }
  131. return {};
  132. },
  133. sendToMailContentScriptResilient: async (_mail, message, options) => {
  134. mailPollCalls.push({
  135. payload: message.payload,
  136. options,
  137. });
  138. return { code: '654321', emailTimestamp: 123 };
  139. },
  140. setState: async () => {},
  141. setStepStatus: async () => {},
  142. sleepWithStop: async () => {},
  143. throwIfStopped: () => {},
  144. VERIFICATION_POLL_MAX_ROUNDS: 5,
  145. });
  146. await helpers.resolveVerificationStep(
  147. 8,
  148. {
  149. email: 'user@example.com',
  150. lastLoginCode: null,
  151. },
  152. { provider: 'qq', label: 'QQ 邮箱' },
  153. {
  154. getRemainingTimeMs: async () => 5000,
  155. resendIntervalMs: 0,
  156. }
  157. );
  158. assert.ok(mailPollCalls.length >= 1);
  159. assert.equal(mailPollCalls[0].options.timeoutMs, 5000);
  160. assert.equal(mailPollCalls[0].options.responseTimeoutMs, 5000);
  161. assert.equal(mailPollCalls[0].payload.maxAttempts, 2);
  162. });
  163. test('verification flow keeps Hotmail request timestamp filtering on the first poll', async () => {
  164. const pollPayloads = [];
  165. const helpers = api.createVerificationFlowHelpers({
  166. addLog: async () => {},
  167. chrome: { tabs: { update: async () => {} } },
  168. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  169. completeStepFromBackground: async () => {},
  170. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  171. getHotmailVerificationPollConfig: () => ({}),
  172. getHotmailVerificationRequestTimestamp: () => 87654,
  173. getState: async () => ({}),
  174. getTabId: async () => 1,
  175. HOTMAIL_PROVIDER: 'hotmail-api',
  176. isStopError: () => false,
  177. LUCKMAIL_PROVIDER: 'luckmail-api',
  178. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  179. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  180. pollCloudflareTempEmailVerificationCode: async () => ({}),
  181. pollHotmailVerificationCode: async (_step, _state, payload) => {
  182. pollPayloads.push(payload);
  183. return { code: '654321', emailTimestamp: 123 };
  184. },
  185. pollLuckmailVerificationCode: async () => ({}),
  186. sendToContentScript: async (_source, message) => {
  187. if (message.type === 'FILL_CODE') {
  188. return {};
  189. }
  190. return {};
  191. },
  192. sendToMailContentScriptResilient: async () => ({}),
  193. setState: async () => {},
  194. setStepStatus: async () => {},
  195. sleepWithStop: async () => {},
  196. throwIfStopped: () => {},
  197. VERIFICATION_POLL_MAX_ROUNDS: 5,
  198. });
  199. await helpers.resolveVerificationStep(
  200. 7,
  201. {
  202. email: 'user@example.com',
  203. loginVerificationRequestedAt: 100000,
  204. lastLoginCode: null,
  205. },
  206. { provider: 'hotmail-api', label: 'Hotmail' },
  207. {}
  208. );
  209. assert.equal(pollPayloads.length, 1);
  210. assert.equal(pollPayloads[0].filterAfterTimestamp, 87654);
  211. });
  212. test('verification flow keeps fixed filter timestamp after step 4 resend', async () => {
  213. const pollPayloads = [];
  214. let submitCount = 0;
  215. const helpers = api.createVerificationFlowHelpers({
  216. addLog: async () => {},
  217. chrome: { tabs: { update: async () => {} } },
  218. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  219. completeStepFromBackground: async () => {},
  220. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  221. getHotmailVerificationPollConfig: () => ({}),
  222. getHotmailVerificationRequestTimestamp: (_step, state) => Math.max(0, Number(state.signupVerificationRequestedAt || 0) - 15000),
  223. getState: async () => ({}),
  224. getTabId: async () => 1,
  225. HOTMAIL_PROVIDER: 'hotmail-api',
  226. isStopError: () => false,
  227. LUCKMAIL_PROVIDER: 'luckmail-api',
  228. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  229. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  230. pollCloudflareTempEmailVerificationCode: async () => ({}),
  231. pollHotmailVerificationCode: async (_step, _state, payload) => {
  232. pollPayloads.push(payload);
  233. return {
  234. code: pollPayloads.length === 1 ? '111111' : '222222',
  235. emailTimestamp: pollPayloads.length,
  236. };
  237. },
  238. pollLuckmailVerificationCode: async () => ({}),
  239. sendToContentScript: async (_source, message) => {
  240. if (message.type === 'FILL_CODE') {
  241. submitCount += 1;
  242. return submitCount === 1
  243. ? { invalidCode: true, errorText: '旧验证码' }
  244. : {};
  245. }
  246. if (message.type === 'RESEND_VERIFICATION_CODE') {
  247. return {};
  248. }
  249. return {};
  250. },
  251. sendToMailContentScriptResilient: async () => ({}),
  252. setState: async () => {},
  253. setStepStatus: async () => {},
  254. sleepWithStop: async () => {},
  255. throwIfStopped: () => {},
  256. VERIFICATION_POLL_MAX_ROUNDS: 5,
  257. });
  258. await helpers.resolveVerificationStep(
  259. 4,
  260. {
  261. email: 'user@example.com',
  262. signupVerificationRequestedAt: 100000,
  263. lastSignupCode: null,
  264. },
  265. { provider: 'hotmail-api', label: 'Hotmail' },
  266. {
  267. filterAfterTimestamp: 123456,
  268. }
  269. );
  270. assert.equal(pollPayloads.length, 2);
  271. assert.equal(pollPayloads[0].filterAfterTimestamp, 123456);
  272. assert.equal(pollPayloads[1].filterAfterTimestamp, 123456);
  273. });
  274. test('verification flow uses configured signup resend count for step 4', async () => {
  275. const resendSteps = [];
  276. let pollCalls = 0;
  277. const helpers = api.createVerificationFlowHelpers({
  278. addLog: async () => {},
  279. chrome: { tabs: { update: async () => {} } },
  280. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  281. completeStepFromBackground: async () => {},
  282. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  283. getHotmailVerificationPollConfig: () => ({}),
  284. getHotmailVerificationRequestTimestamp: () => 0,
  285. getState: async () => ({}),
  286. getTabId: async () => 1,
  287. HOTMAIL_PROVIDER: 'hotmail-api',
  288. isStopError: () => false,
  289. LUCKMAIL_PROVIDER: 'luckmail-api',
  290. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  291. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  292. pollCloudflareTempEmailVerificationCode: async () => ({}),
  293. pollHotmailVerificationCode: async () => ({}),
  294. pollLuckmailVerificationCode: async () => ({}),
  295. sendToContentScript: async (_source, message) => {
  296. if (message.type === 'RESEND_VERIFICATION_CODE') {
  297. resendSteps.push(message.step);
  298. }
  299. return {};
  300. },
  301. sendToMailContentScriptResilient: async () => {
  302. pollCalls += 1;
  303. return pollCalls === 2
  304. ? { code: '654321', emailTimestamp: 123 }
  305. : {};
  306. },
  307. setState: async () => {},
  308. setStepStatus: async () => {},
  309. sleepWithStop: async () => {},
  310. throwIfStopped: () => {},
  311. VERIFICATION_POLL_MAX_ROUNDS: 5,
  312. });
  313. await helpers.resolveVerificationStep(
  314. 4,
  315. {
  316. email: 'user@example.com',
  317. verificationResendCount: 2,
  318. lastSignupCode: null,
  319. },
  320. { provider: 'qq', label: 'QQ 邮箱' },
  321. {
  322. requestFreshCodeFirst: true,
  323. resendIntervalMs: 0,
  324. }
  325. );
  326. assert.deepStrictEqual(resendSteps, [4, 4]);
  327. assert.equal(pollCalls, 2);
  328. });
  329. test('verification flow uses configured login resend count for step 8', async () => {
  330. const resendSteps = [];
  331. let pollCalls = 0;
  332. const helpers = api.createVerificationFlowHelpers({
  333. addLog: async () => {},
  334. chrome: { tabs: { update: async () => {} } },
  335. CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
  336. completeStepFromBackground: async () => {},
  337. confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
  338. getHotmailVerificationPollConfig: () => ({}),
  339. getHotmailVerificationRequestTimestamp: () => 0,
  340. getState: async () => ({}),
  341. getTabId: async () => 1,
  342. HOTMAIL_PROVIDER: 'hotmail-api',
  343. isStopError: () => false,
  344. LUCKMAIL_PROVIDER: 'luckmail-api',
  345. MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
  346. MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
  347. pollCloudflareTempEmailVerificationCode: async () => ({}),
  348. pollHotmailVerificationCode: async () => ({}),
  349. pollLuckmailVerificationCode: async () => ({}),
  350. sendToContentScript: async (_source, message) => {
  351. if (message.type === 'RESEND_VERIFICATION_CODE') {
  352. resendSteps.push(message.step);
  353. }
  354. return {};
  355. },
  356. sendToMailContentScriptResilient: async () => {
  357. pollCalls += 1;
  358. return pollCalls === 3
  359. ? { code: '654321', emailTimestamp: 123 }
  360. : {};
  361. },
  362. setState: async () => {},
  363. setStepStatus: async () => {},
  364. sleepWithStop: async () => {},
  365. throwIfStopped: () => {},
  366. VERIFICATION_POLL_MAX_ROUNDS: 5,
  367. });
  368. await helpers.resolveVerificationStep(
  369. 8,
  370. {
  371. email: 'user@example.com',
  372. verificationResendCount: 2,
  373. lastLoginCode: null,
  374. },
  375. { provider: 'qq', label: 'QQ 邮箱' },
  376. {
  377. requestFreshCodeFirst: false,
  378. resendIntervalMs: 0,
  379. }
  380. );
  381. assert.deepStrictEqual(resendSteps, [8, 8]);
  382. assert.equal(pollCalls, 3);
  383. });