@coderoftime/fints 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/CHANGELOG.md +519 -1
  2. package/README.md +138 -40
  3. package/dist/client.d.ts +49 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +242 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/core/account.d.ts +46 -7
  8. package/dist/core/account.d.ts.map +1 -1
  9. package/dist/core/account.js +143 -24
  10. package/dist/core/account.js.map +1 -1
  11. package/dist/core/amount.d.ts +15 -4
  12. package/dist/core/amount.d.ts.map +1 -1
  13. package/dist/core/amount.js +46 -9
  14. package/dist/core/amount.js.map +1 -1
  15. package/dist/core/balance.d.ts +4 -0
  16. package/dist/core/balance.d.ts.map +1 -1
  17. package/dist/core/balance.js +11 -5
  18. package/dist/core/balance.js.map +1 -1
  19. package/dist/core/bank-info.d.ts +21 -4
  20. package/dist/core/bank-info.d.ts.map +1 -1
  21. package/dist/core/bank-info.js +26 -5
  22. package/dist/core/bank-info.js.map +1 -1
  23. package/dist/core/client.d.ts +81 -19
  24. package/dist/core/client.d.ts.map +1 -1
  25. package/dist/core/config.d.ts +40 -20
  26. package/dist/core/config.d.ts.map +1 -1
  27. package/dist/core/config.js +85 -30
  28. package/dist/core/config.js.map +1 -1
  29. package/dist/core/errors.d.ts +100 -26
  30. package/dist/core/errors.d.ts.map +1 -1
  31. package/dist/core/errors.js +136 -53
  32. package/dist/core/errors.js.map +1 -1
  33. package/dist/core/feedback.d.ts +11 -1
  34. package/dist/core/feedback.d.ts.map +1 -1
  35. package/dist/core/feedback.js +14 -2
  36. package/dist/core/feedback.js.map +1 -1
  37. package/dist/core/transaction.d.ts +11 -1
  38. package/dist/core/transaction.d.ts.map +1 -1
  39. package/dist/core/transaction.js +21 -4
  40. package/dist/core/transaction.js.map +1 -1
  41. package/dist/core/transport.d.ts +48 -6
  42. package/dist/core/transport.d.ts.map +1 -1
  43. package/dist/core/transport.js +126 -29
  44. package/dist/core/transport.js.map +1 -1
  45. package/dist/formats/camt.d.ts +28 -5
  46. package/dist/formats/camt.d.ts.map +1 -1
  47. package/dist/formats/camt.js +132 -50
  48. package/dist/formats/camt.js.map +1 -1
  49. package/dist/formats/mt940.d.ts +29 -5
  50. package/dist/formats/mt940.d.ts.map +1 -1
  51. package/dist/formats/mt940.js +209 -113
  52. package/dist/formats/mt940.js.map +1 -1
  53. package/dist/formats/readers.d.ts +6 -2
  54. package/dist/formats/readers.d.ts.map +1 -1
  55. package/dist/formats/readers.js +28 -29
  56. package/dist/formats/readers.js.map +1 -1
  57. package/dist/formats/record-budget.d.ts +7 -0
  58. package/dist/formats/record-budget.d.ts.map +1 -0
  59. package/dist/formats/record-budget.js +17 -0
  60. package/dist/formats/record-budget.js.map +1 -0
  61. package/dist/formats/sepa-purpose.d.ts +9 -0
  62. package/dist/formats/sepa-purpose.d.ts.map +1 -1
  63. package/dist/formats/sepa-purpose.js +32 -2
  64. package/dist/formats/sepa-purpose.js.map +1 -1
  65. package/dist/index.d.ts +3 -3
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +2 -2
  68. package/dist/index.js.map +1 -1
  69. package/dist/session/app-approval.d.ts +11 -5
  70. package/dist/session/app-approval.d.ts.map +1 -1
  71. package/dist/session/app-approval.js +23 -12
  72. package/dist/session/app-approval.js.map +1 -1
  73. package/dist/session/dialog-runner.d.ts +11 -11
  74. package/dist/session/dialog-runner.d.ts.map +1 -1
  75. package/dist/session/dialog-runner.js +34 -19
  76. package/dist/session/dialog-runner.js.map +1 -1
  77. package/dist/session/login-guard.d.ts +54 -49
  78. package/dist/session/login-guard.d.ts.map +1 -1
  79. package/dist/session/login-guard.js +170 -83
  80. package/dist/session/login-guard.js.map +1 -1
  81. package/dist/session/sepa-accounts.d.ts +20 -14
  82. package/dist/session/sepa-accounts.d.ts.map +1 -1
  83. package/dist/session/sepa-accounts.js +62 -46
  84. package/dist/session/sepa-accounts.js.map +1 -1
  85. package/dist/session/session-setup.d.ts +41 -24
  86. package/dist/session/session-setup.d.ts.map +1 -1
  87. package/dist/session/session-setup.js +106 -63
  88. package/dist/session/session-setup.js.map +1 -1
  89. package/dist/session/session-state.d.ts +10 -6
  90. package/dist/session/session-state.d.ts.map +1 -1
  91. package/dist/session/session-state.js +72 -18
  92. package/dist/session/session-state.js.map +1 -1
  93. package/dist/session/signed-dialog.d.ts +83 -0
  94. package/dist/session/signed-dialog.d.ts.map +1 -0
  95. package/dist/session/signed-dialog.js +185 -0
  96. package/dist/session/signed-dialog.js.map +1 -0
  97. package/dist/session/tan-selection.d.ts +26 -18
  98. package/dist/session/tan-selection.d.ts.map +1 -1
  99. package/dist/session/tan-selection.js +44 -33
  100. package/dist/session/tan-selection.js.map +1 -1
  101. package/dist/session/user-prompts.d.ts +14 -5
  102. package/dist/session/user-prompts.d.ts.map +1 -1
  103. package/dist/session/user-prompts.js +53 -18
  104. package/dist/session/user-prompts.js.map +1 -1
  105. package/dist/tan/challenge.d.ts +47 -24
  106. package/dist/tan/challenge.d.ts.map +1 -1
  107. package/dist/tan/challenge.js +56 -51
  108. package/dist/tan/challenge.js.map +1 -1
  109. package/dist/tan/flicker.d.ts +11 -1
  110. package/dist/tan/flicker.d.ts.map +1 -1
  111. package/dist/tan/flicker.js +70 -24
  112. package/dist/tan/flicker.js.map +1 -1
  113. package/dist/tan/tan-image.d.ts +6 -2
  114. package/dist/tan/tan-image.d.ts.map +1 -1
  115. package/dist/tan/tan-image.js +14 -6
  116. package/dist/tan/tan-image.js.map +1 -1
  117. package/dist/tan/tan-models.d.ts +14 -2
  118. package/dist/tan/tan-models.d.ts.map +1 -1
  119. package/dist/tan/tan-models.js +24 -7
  120. package/dist/tan/tan-models.js.map +1 -1
  121. package/dist/util/arguments.d.ts +13 -0
  122. package/dist/util/arguments.d.ts.map +1 -0
  123. package/dist/util/arguments.js +40 -0
  124. package/dist/util/arguments.js.map +1 -0
  125. package/dist/util/async.d.ts +4 -3
  126. package/dist/util/async.d.ts.map +1 -1
  127. package/dist/util/async.js +6 -7
  128. package/dist/util/async.js.map +1 -1
  129. package/dist/util/bytes.d.ts +3 -4
  130. package/dist/util/bytes.d.ts.map +1 -1
  131. package/dist/util/bytes.js +13 -21
  132. package/dist/util/bytes.js.map +1 -1
  133. package/dist/util/call-context.d.ts.map +1 -1
  134. package/dist/util/call-context.js +3 -2
  135. package/dist/util/call-context.js.map +1 -1
  136. package/dist/util/dates.d.ts +6 -20
  137. package/dist/util/dates.d.ts.map +1 -1
  138. package/dist/util/dates.js +58 -28
  139. package/dist/util/dates.js.map +1 -1
  140. package/dist/util/errors.d.ts +13 -3
  141. package/dist/util/errors.d.ts.map +1 -1
  142. package/dist/util/errors.js +19 -6
  143. package/dist/util/errors.js.map +1 -1
  144. package/dist/util/immutable-map.d.ts +5 -2
  145. package/dist/util/immutable-map.d.ts.map +1 -1
  146. package/dist/util/immutable-map.js +15 -12
  147. package/dist/util/immutable-map.js.map +1 -1
  148. package/dist/util/inspect.d.ts +11 -0
  149. package/dist/util/inspect.d.ts.map +1 -0
  150. package/dist/util/inspect.js +14 -0
  151. package/dist/util/inspect.js.map +1 -0
  152. package/dist/util/json.d.ts +4 -0
  153. package/dist/util/json.d.ts.map +1 -1
  154. package/dist/util/json.js +18 -0
  155. package/dist/util/json.js.map +1 -1
  156. package/dist/util/text.d.ts +38 -5
  157. package/dist/util/text.d.ts.map +1 -1
  158. package/dist/util/text.js +90 -16
  159. package/dist/util/text.js.map +1 -1
  160. package/dist/util/xml.d.ts +9 -6
  161. package/dist/util/xml.d.ts.map +1 -1
  162. package/dist/util/xml.js +129 -56
  163. package/dist/util/xml.js.map +1 -1
  164. package/dist/v3/account-extension.d.ts.map +1 -1
  165. package/dist/v3/account-extension.js +7 -10
  166. package/dist/v3/account-extension.js.map +1 -1
  167. package/dist/v3/bank-connection.d.ts +54 -0
  168. package/dist/v3/bank-connection.d.ts.map +1 -0
  169. package/dist/v3/bank-connection.js +118 -0
  170. package/dist/v3/bank-connection.js.map +1 -0
  171. package/dist/v3/dialog.d.ts +84 -46
  172. package/dist/v3/dialog.d.ts.map +1 -1
  173. package/dist/v3/dialog.js +174 -60
  174. package/dist/v3/dialog.js.map +1 -1
  175. package/dist/v3/message.d.ts +32 -11
  176. package/dist/v3/message.d.ts.map +1 -1
  177. package/dist/v3/message.js +144 -83
  178. package/dist/v3/message.js.map +1 -1
  179. package/dist/v3/parameter-store.d.ts +111 -0
  180. package/dist/v3/parameter-store.d.ts.map +1 -0
  181. package/dist/v3/parameter-store.js +197 -0
  182. package/dist/v3/parameter-store.js.map +1 -0
  183. package/dist/v3/parameters.d.ts +44 -17
  184. package/dist/v3/parameters.d.ts.map +1 -1
  185. package/dist/v3/parameters.js +136 -60
  186. package/dist/v3/parameters.js.map +1 -1
  187. package/dist/v3/protocol.d.ts +50 -50
  188. package/dist/v3/protocol.d.ts.map +1 -1
  189. package/dist/v3/protocol.js +91 -210
  190. package/dist/v3/protocol.js.map +1 -1
  191. package/dist/v3/queries.d.ts +51 -0
  192. package/dist/v3/queries.d.ts.map +1 -0
  193. package/dist/v3/queries.js +128 -0
  194. package/dist/v3/queries.js.map +1 -0
  195. package/dist/v3/segments.d.ts +43 -19
  196. package/dist/v3/segments.d.ts.map +1 -1
  197. package/dist/v3/segments.js +84 -33
  198. package/dist/v3/segments.js.map +1 -1
  199. package/dist/v3/syntax.d.ts +68 -13
  200. package/dist/v3/syntax.d.ts.map +1 -1
  201. package/dist/v3/syntax.js +183 -102
  202. package/dist/v3/syntax.js.map +1 -1
  203. package/package.json +10 -6
  204. package/src/client.ts +293 -0
  205. package/src/core/account.ts +153 -24
  206. package/src/core/amount.ts +45 -10
  207. package/src/core/balance.ts +12 -5
  208. package/src/core/bank-info.ts +29 -5
  209. package/src/core/client.ts +83 -19
  210. package/src/core/config.ts +95 -35
  211. package/src/core/errors.ts +148 -54
  212. package/src/core/feedback.ts +16 -2
  213. package/src/core/transaction.ts +22 -4
  214. package/src/core/transport.ts +153 -32
  215. package/src/formats/camt.ts +153 -45
  216. package/src/formats/mt940.ts +244 -122
  217. package/src/formats/readers.ts +28 -31
  218. package/src/formats/record-budget.ts +18 -0
  219. package/src/formats/sepa-purpose.ts +29 -2
  220. package/src/index.ts +4 -2
  221. package/src/session/app-approval.ts +51 -38
  222. package/src/session/dialog-runner.ts +61 -31
  223. package/src/session/login-guard.ts +189 -87
  224. package/src/session/sepa-accounts.ts +63 -45
  225. package/src/session/session-setup.ts +105 -68
  226. package/src/session/session-state.ts +74 -23
  227. package/src/session/signed-dialog.ts +222 -0
  228. package/src/session/tan-selection.ts +50 -39
  229. package/src/session/user-prompts.ts +62 -18
  230. package/src/tan/challenge.ts +87 -64
  231. package/src/tan/flicker.ts +72 -23
  232. package/src/tan/tan-image.ts +15 -6
  233. package/src/tan/tan-models.ts +26 -7
  234. package/src/util/arguments.ts +40 -0
  235. package/src/util/async.ts +10 -9
  236. package/src/util/bytes.ts +14 -20
  237. package/src/util/call-context.ts +3 -2
  238. package/src/util/dates.ts +64 -33
  239. package/src/util/errors.ts +22 -6
  240. package/src/util/immutable-map.ts +15 -12
  241. package/src/util/inspect.ts +14 -0
  242. package/src/util/json.ts +16 -0
  243. package/src/util/text.ts +87 -15
  244. package/src/util/xml.ts +130 -52
  245. package/src/v3/account-extension.ts +7 -12
  246. package/src/v3/bank-connection.ts +126 -0
  247. package/src/v3/dialog.ts +186 -92
  248. package/src/v3/message.ts +140 -79
  249. package/src/v3/parameter-store.ts +226 -0
  250. package/src/v3/parameters.ts +166 -66
  251. package/src/v3/protocol.ts +103 -253
  252. package/src/v3/queries.ts +198 -0
  253. package/src/v3/segments.ts +86 -31
  254. package/src/v3/syntax.ts +172 -93
  255. package/dist/session/bank-connection.d.ts +0 -28
  256. package/dist/session/bank-connection.d.ts.map +0 -1
  257. package/dist/session/bank-connection.js +0 -48
  258. package/dist/session/bank-connection.js.map +0 -1
  259. package/dist/session/parameter-data.d.ts +0 -83
  260. package/dist/session/parameter-data.d.ts.map +0 -1
  261. package/dist/session/parameter-data.js +0 -68
  262. package/dist/session/parameter-data.js.map +0 -1
  263. package/dist/session/pin-tan-client.d.ts +0 -54
  264. package/dist/session/pin-tan-client.d.ts.map +0 -1
  265. package/dist/session/pin-tan-client.js +0 -153
  266. package/dist/session/pin-tan-client.js.map +0 -1
  267. package/dist/session/pin-tan-dialog.d.ts +0 -71
  268. package/dist/session/pin-tan-dialog.d.ts.map +0 -1
  269. package/dist/session/pin-tan-dialog.js +0 -124
  270. package/dist/session/pin-tan-dialog.js.map +0 -1
  271. package/dist/session/protocol.d.ts +0 -209
  272. package/dist/session/protocol.d.ts.map +0 -1
  273. package/dist/session/protocol.js +0 -75
  274. package/dist/session/protocol.js.map +0 -1
  275. package/dist/session/response-handler.d.ts +0 -48
  276. package/dist/session/response-handler.d.ts.map +0 -1
  277. package/dist/session/response-handler.js +0 -121
  278. package/dist/session/response-handler.js.map +0 -1
  279. package/dist/v3/client.d.ts +0 -24
  280. package/dist/v3/client.d.ts.map +0 -1
  281. package/dist/v3/client.js +0 -16
  282. package/dist/v3/client.js.map +0 -1
  283. package/src/session/bank-connection.ts +0 -51
  284. package/src/session/parameter-data.ts +0 -137
  285. package/src/session/pin-tan-client.ts +0 -186
  286. package/src/session/pin-tan-dialog.ts +0 -160
  287. package/src/session/protocol.ts +0 -311
  288. package/src/session/response-handler.ts +0 -130
  289. package/src/v3/client.ts +0 -29
@@ -2,7 +2,17 @@ import { FinTsBankError, FinTsProtocolError, parseBankData } from '../core/error
2
2
  import { continuations, FeedbackCodes, hasCode } from '../core/feedback.js';
3
3
  /** Most pages the client fetches for one query, far more than banks send. */
4
4
  const maxPages = 1000;
5
- /** Most data one query may hold, summed over its pages: the transport bounds a single response only. */
5
+ /**
6
+ * Most further pages of one query that may ask for an approval, as its first page may: enough for a bank that asks anew
7
+ * for each page of a query of four pages, while the user approves four times per query at most, and loses the pages
8
+ * approved so far with any approval they cancel.
9
+ */
10
+ const maxPageApprovals = 3;
11
+ /**
12
+ * Most data one query may hold, summed over its pages as `IncomingMessage3.size` measures it: the transport and the
13
+ * parser bound a single response only. The statements and transactions read from them have a budget of their own
14
+ * (`RecordBudget`).
15
+ */
6
16
  const maxQueryMebibytes = 128;
7
17
  /**
8
18
  * Keeps one dialog open for all calls and runs the queries in it.
@@ -13,13 +23,9 @@ const maxQueryMebibytes = 128;
13
23
  */
14
24
  export class DialogRunner {
15
25
  setup;
16
- responses;
17
- prompts;
18
26
  dialog;
19
- constructor(setup, responses, prompts) {
27
+ constructor(setup) {
20
28
  this.setup = setup;
21
- this.responses = responses;
22
- this.prompts = prompts;
23
29
  }
24
30
  /** The open dialog, or a new one once the setup is complete and the user has passed strong authentication. */
25
31
  async open() {
@@ -34,7 +40,7 @@ export class DialogRunner {
34
40
  async login() {
35
41
  const dialog = await this.setup.newDialog();
36
42
  try {
37
- await this.responses.startDialog(dialog, () => dialog.start());
43
+ await dialog.login();
38
44
  return dialog;
39
45
  }
40
46
  catch (error) {
@@ -61,31 +67,43 @@ export class DialogRunner {
61
67
  const pages = await this.fetchPages(dialog, query.order);
62
68
  return parseBankData(() => query.read(pages));
63
69
  }
64
- /** Sends `order` in `dialog` and completes its approval. */
65
- async run(dialog, order) {
66
- this.prompts.checkOpen();
70
+ /** Sends `order` in `dialog` and completes its approval, which `admitApproval` may refuse (`SignedDialog.send`). */
71
+ async run(dialog, order, admitApproval) {
67
72
  try {
68
- return await this.responses.complete(dialog, await dialog.send(order));
73
+ return await dialog.send(order, admitApproval);
69
74
  }
70
75
  catch (error) {
71
76
  // The bank rejected the order and the dialog stays usable, or the bank ended the dialog or rejected the PIN and
72
- // the dialog is discarded already. A cancelled or failed approval, or a broken connection, leaves the dialog in an
73
- // unknown state.
77
+ // the dialog is discarded already. A cancelled, failed or refused approval, or a broken connection, leaves the
78
+ // dialog in an unknown state.
74
79
  if (!(error instanceof FinTsBankError))
75
80
  await this.end();
76
81
  throw error;
77
82
  }
78
83
  }
79
- /** Sends the order built by `order` until the bank has delivered all pages (Formals B.6.3, "Aufsetzpunkt"). */
84
+ /**
85
+ * Sends the order built by `order` until the bank has delivered all pages (Formals B.6.3, "Aufsetzpunkt"), and
86
+ * completes the approval each asks for, that of `maxPageApprovals` further pages at most.
87
+ */
80
88
  async fetchPages(dialog, order) {
81
89
  const pages = [];
82
90
  const seen = new Set();
83
91
  let size = 0;
92
+ let pageApprovals = 0;
93
+ const admitPageApproval = () => {
94
+ if (pageApprovals === maxPageApprovals) {
95
+ throw new FinTsProtocolError(`The bank asks for an approval of more than ${maxPageApprovals} further pages of one query; ask for a shorter period`);
96
+ }
97
+ pageApprovals++;
98
+ };
84
99
  let continuation;
85
100
  for (;;) {
86
- const page = await this.run(dialog, order(continuation));
101
+ const page = await this.run(dialog, order(continuation), continuation === undefined ? undefined : admitPageApproval);
87
102
  pages.push(page);
88
103
  size += page.size;
104
+ if (size > maxQueryMebibytes * 1024 * 1024) {
105
+ throw new FinTsProtocolError(`The bank sends more than ${maxQueryMebibytes} MiB for one query; ask for a shorter period`);
106
+ }
89
107
  const positions = [...continuations(page.feedback)];
90
108
  // A message carries one order, so the bank names one position at most.
91
109
  if (positions.length > 1)
@@ -98,14 +116,11 @@ export class DialogRunner {
98
116
  }
99
117
  return pages;
100
118
  }
101
- // A repeated position or endless pages would keep the client busy forever, and unbounded pages exhaust memory.
119
+ // A repeated position or endless pages would keep the client busy forever.
102
120
  if (seen.has(continuation) || pages.length === maxPages) {
103
121
  throw new FinTsProtocolError('The bank does not stop sending pages');
104
122
  }
105
123
  seen.add(continuation);
106
- if (size > maxQueryMebibytes * 1024 * 1024) {
107
- throw new FinTsProtocolError(`The bank sends more than ${maxQueryMebibytes} MiB for one query; ask for a shorter period`);
108
- }
109
124
  }
110
125
  }
111
126
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-runner.js","sourceRoot":"","sources":["../../src/session/dialog-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dialog-runner.js","sourceRoot":"","sources":["../../src/session/dialog-runner.ts"],"names":[],"mappings":"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"}
@@ -1,73 +1,78 @@
1
- import { FinTsBankError } from '../core/errors.js';
1
+ import { FinTsLoginRefusedError, type FinTsBankError } from '../core/errors.js';
2
+ import { type FinTsFeedback } from '../core/feedback.js';
2
3
  import type { TanMethod } from '../tan/tan-models.js';
4
+ import type { PinGuard } from '../v3/protocol.js';
3
5
  /**
4
6
  * The kind of request a bank response answers, which decides what its errors mean for the login (`LoginGuard.judge`)
5
7
  * and whether they can reject a TAN:
6
- * - `login`: the login that opens a dialog;
7
- * - `loginWithOrder`: a login that carries an order in the same message, whose errors may concern the order alone;
8
+ * - `login`: the login that opens a dialog, which carries no order;
8
9
  * - `order`: an order in a dialog the bank has opened;
9
10
  * - `approval`: a message that completes an approval, the TAN or the question whether the user approved in the app.
10
11
  */
11
- export type RequestKind = 'login' | 'loginWithOrder' | 'order' | 'approval';
12
+ export type RequestKind = 'login' | 'order' | 'approval';
12
13
  /**
13
14
  * A reason for a refused login that the session can take care of by itself, after which it logs in once more: the user
14
15
  * may not use the TAN method the login was signed with, or the bank no longer accepts the customer system ID.
15
16
  */
16
17
  export type RefusalReason = 'tanMethodNotAllowed' | 'systemIdRejected';
18
+ /** What the saved state keeps of the guard: the return codes of the bank's refusal, and `unreadLogins`. */
19
+ export interface LoginGuardState {
20
+ readonly refusal: readonly FinTsFeedback[] | undefined;
21
+ readonly unreadLogins: number;
22
+ }
17
23
  /**
18
- * Keeps a retrying app from locking the user's access: once the bank has refused a login, the client does not send the
19
- * PIN again.
20
- *
21
- * Banks lock the access after a few failed attempts, and a return code does not always tell whether the PIN was the
22
- * reason. So `judge` counts as a refused login:
23
- * - a rejected PIN, wherever the bank reports it;
24
- * - any other error in the answer to a login, unless the login carries an order and the bank keeps the dialog open:
25
- * then the error concerns the order.
26
- *
27
- * Errors in the answer to an order or an approval are no refusal, such as a wrong TAN at login: the bank checks the PIN
28
- * before it asks for the approval. New login data (return code 3072) end the old ones for good (`refuse`).
24
+ * Holds the PIN and hands it out only while no login is refused (`pin`), so that a retrying app cannot lock the user's
25
+ * access. `FinTsLoginRefusedError` tells which logins count as refused; `judge` decides it for each answer.
29
26
  *
30
- * The guard fails closed: a refusal counts from the moment `judge` sees it, and only `pardon` lifts it, once the
31
- * session has taken care of a `RefusalReason` the refusal names (`remediableReasons`). The protocol calls `check`
32
- * before every message it signs with the PIN.
27
+ * A refusal counts at once and fails closed: only `remedied` lifts it, within the call that met it (`runCall`), once
28
+ * the session has taken care of a reason it names, or has failed the call trying, each reason once per call, so that a
29
+ * call sends at most three logins the bank refuses. A refusal that outlasts the call is final, and the guard forgets
30
+ * the PIN; only a PIN the user enters anew lifts it (`replacePin`), unless it brings new login data.
33
31
  */
34
- export declare class LoginGuard {
35
- private refusal;
36
- /** The TAN method of the refused login; `undefined` without a refusal, and for new login data. */
37
- private refusedMethodValue;
38
- /** Throws the bank's refusal of an earlier login, so that the PIN is not sent again. */
32
+ export declare class LoginGuard implements PinGuard {
33
+ #private;
34
+ private refusalValue;
35
+ /** The TAN method of the login `refusalValue` refused, which a restored refusal does not tell. */
36
+ private refusedMethod;
37
+ /** Logins sent since the bank last took the PIN whose answer the client has not read. */
38
+ private unreadLogins;
39
+ /** The refusal of `maxUnreadLogins` unread logins, kept so that `check` throws the same error each time. */
40
+ private unreadLoginsRefusal;
41
+ /** The reasons `remedied` has lifted a refusal for in the running call. */
42
+ private readonly pardonedReasons;
43
+ constructor(pin: string);
44
+ /** The PIN to sign a message with; throws the refusal instead (`check`), and `FinTsStateError` once closed. */
45
+ get pin(): string;
46
+ /** Throws the refusal, if there is one. */
39
47
  check(): void;
40
- /** Whether the guard holds a refusal. */
41
- get hasRefusal(): boolean;
42
- /** The TAN method the refused login was signed with; `undefined` where the guard holds no refusal of a login. */
43
- get refusedMethod(): TanMethod | undefined;
48
+ /** The bank's refusal, else that of `maxUnreadLogins` unread logins; `undefined` while there is none. */
49
+ get refusal(): FinTsLoginRefusedError | undefined;
50
+ /** Counts a login signed with the PIN as unread until `judge` reads its answer. */
51
+ loginSent(): void;
44
52
  /**
45
- * Whether `error`, raised for the answer to a request of `kind` in a dialog signed with `method`, refuses the login;
46
- * records it if it does.
47
- *
48
- * `dialogOpen` tells whether the bank keeps the dialog open after its answer.
53
+ * Judges `error`, if the answer to a request of `kind` in a dialog signed with `method` has one: returns the refusal
54
+ * of the login it amounts to, and records it unless the guard holds one already. Any error refuses a login; after the
55
+ * login only one that refuses it itself, a rejected PIN or new login data, since the bank checked the PIN before.
49
56
  */
50
- judge(error: FinTsBankError, kind: RequestKind, dialogOpen: boolean, method: TanMethod): boolean;
51
- /** Records `error` as the reason the client logs in no more, unless it holds a refusal already. */
52
- refuse(error: FinTsBankError): void;
57
+ judge(error: FinTsBankError | undefined, kind: RequestKind, method: TanMethod): FinTsLoginRefusedError | undefined;
58
+ /** Runs `call`, one call of the client; a refusal that outlasts it is final, and the next call remedies anew. */
59
+ runCall<T>(call: () => Promise<T>): Promise<T>;
53
60
  /**
54
- * Runs `lookup`, which only completes data, such as the SEPA account list; `undefined` where the bank rejects it, so
55
- * that the caller carries on without it.
56
- *
57
- * A refused login is thrown, and so is a wrong TAN, which the user must hear of. An aborted dialog counts as a
58
- * rejection: banks end the dialog along with many.
61
+ * Runs `attempt`, which may log in, and runs it once more each time `remedy` takes care of a reason the refusal it
62
+ * failed with names; `remedy` gets the reason and the TAN method of the refused login, and returns whether it could.
59
63
  */
60
- tryLookUp<T>(lookup: () => Promise<T>): Promise<T | undefined>;
64
+ remedied<T>(attempt: () => Promise<T>, remedy: (reason: RefusalReason, rejected: TanMethod) => Promise<boolean>): Promise<T>;
61
65
  /**
62
- * The reasons the session can take care of in `error`: 3920 without the TAN method of the refused login, 9075 after
63
- * the one-step method, and 9391. Empty if `error` is not the refusal the guard holds, rejects the PIN, or brings new
64
- * login data.
65
- *
66
- * Only the return codes of the refusal itself count: TAN methods listed earlier say nothing about this login.
66
+ * Signs with `pin` from now on, a PIN the user has entered anew and the protocol can send, and lifts the refusal.
67
+ * Throws the refusal instead where it brings new login data, which end the old ones for good.
67
68
  */
68
- remediableReasons(error: FinTsBankError): RefusalReason[];
69
- /** Lifts `refusal` once the session has taken care of its reason, so that the next login may send the PIN. */
70
- pardon(refusal: FinTsBankError): void;
71
- private isRefusal;
69
+ replacePin(pin: string): void;
70
+ /** Forgets the PIN; the client calls it once it is closed. */
71
+ close(): void;
72
+ saveState(): LoginGuardState;
73
+ /** Restores what `saveState` returned; a restored refusal is final, also one without return codes. */
74
+ restoreState(saved: LoginGuardState): void;
75
+ private liftRefusal;
76
+ private forgetPinIfRefused;
72
77
  }
73
78
  //# sourceMappingURL=login-guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login-guard.d.ts","sourceRoot":"","sources":["../../src/session/login-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA4D,MAAM,mBAAmB,CAAC;AAE7G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,gBAAgB,GAAG,OAAO,GAAG,UAAU,CAAC;AAE5E;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,qBAAqB,GAAG,kBAAkB,CAAC;AAEvE;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAA6B;IAE5C,kGAAkG;IAClG,OAAO,CAAC,kBAAkB,CAAwB;IAElD,wFAAwF;IACxF,KAAK,IAAI,IAAI;IAIb,yCAAyC;IACzC,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,iHAAiH;IACjH,IAAI,aAAa,IAAI,SAAS,GAAG,SAAS,CAEzC;IAED;;;;;OAKG;IACH,KAAK,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO;IAShG,mGAAmG;IACnG,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAInC;;;;;;OAMG;IACG,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IASpE;;;;;;OAMG;IACH,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,aAAa,EAAE;IAuBzD,8GAA8G;IAC9G,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAMrC,OAAO,CAAC,SAAS;CAGlB"}
1
+ {"version":3,"file":"login-guard.d.ts","sourceRoot":"","sources":["../../src/session/login-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,sBAAsB,EAGtB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA+C,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,qBAAqB,GAAG,kBAAkB,CAAC;AAEvE,2GAA2G;AAC3G,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAQD;;;;;;;;GAQG;AACH,qBAAa,UAAW,YAAW,QAAQ;;IAIzC,OAAO,CAAC,YAAY,CAAqC;IAEzD,kGAAkG;IAClG,OAAO,CAAC,aAAa,CAAwB;IAE7C,yFAAyF;IACzF,OAAO,CAAC,YAAY,CAAK;IAEzB,4GAA4G;IAC5G,OAAO,CAAC,mBAAmB,CAAqC;IAEhE,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA4B;gBAEhD,GAAG,EAAE,MAAM;IAIvB,+GAA+G;IAC/G,IAAI,GAAG,IAAI,MAAM,CAIhB;IAED,2CAA2C;IAC3C,KAAK,IAAI,IAAI;IAKb,yGAAyG;IACzG,IAAI,OAAO,IAAI,sBAAsB,GAAG,SAAS,CAQhD;IAED,mFAAmF;IACnF,SAAS,IAAI,IAAI;IAIjB;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,sBAAsB,GAAG,SAAS;IAYlH,iHAAiH;IAC3G,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IASpD;;;OAGG;IACG,QAAQ,CAAC,CAAC,EACd,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,GACvE,OAAO,CAAC,CAAC,CAAC;IA2Bb;;;OAGG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAQ7B,8DAA8D;IAC9D,KAAK,IAAI,IAAI;IAIb,SAAS,IAAI,eAAe;IAI5B,sGAAsG;IACtG,YAAY,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAW1C,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,kBAAkB;CAG3B"}
@@ -1,109 +1,196 @@
1
- import { FinTsBankError, FinTsLoginDataChangedError, FinTsPinError, FinTsTanError } from '../core/errors.js';
1
+ import { clientClosedError, FinTsLoginDataChangedError, FinTsLoginRefusedError, FinTsPinError, loginRefusalFor, } from '../core/errors.js';
2
2
  import { allowedTanMethodsIn, FeedbackCodes, hasCode } from '../core/feedback.js';
3
3
  /**
4
- * Keeps a retrying app from locking the user's access: once the bank has refused a login, the client does not send the
5
- * PIN again.
6
- *
7
- * Banks lock the access after a few failed attempts, and a return code does not always tell whether the PIN was the
8
- * reason. So `judge` counts as a refused login:
9
- * - a rejected PIN, wherever the bank reports it;
10
- * - any other error in the answer to a login, unless the login carries an order and the bank keeps the dialog open:
11
- * then the error concerns the order.
12
- *
13
- * Errors in the answer to an order or an approval are no refusal, such as a wrong TAN at login: the bank checks the PIN
14
- * before it asks for the approval. New login data (return code 3072) end the old ones for good (`refuse`).
4
+ * Logins in a row whose answer the client could not read, after which it sends the PIN no more: one may be a glitch of
5
+ * the connection, but with two the client would not learn of a wrong PIN before the bank locks the access.
6
+ */
7
+ const maxUnreadLogins = 2;
8
+ /**
9
+ * Holds the PIN and hands it out only while no login is refused (`pin`), so that a retrying app cannot lock the user's
10
+ * access. `FinTsLoginRefusedError` tells which logins count as refused; `judge` decides it for each answer.
15
11
  *
16
- * The guard fails closed: a refusal counts from the moment `judge` sees it, and only `pardon` lifts it, once the
17
- * session has taken care of a `RefusalReason` the refusal names (`remediableReasons`). The protocol calls `check`
18
- * before every message it signs with the PIN.
12
+ * A refusal counts at once and fails closed: only `remedied` lifts it, within the call that met it (`runCall`), once
13
+ * the session has taken care of a reason it names, or has failed the call trying, each reason once per call, so that a
14
+ * call sends at most three logins the bank refuses. A refusal that outlasts the call is final, and the guard forgets
15
+ * the PIN; only a PIN the user enters anew lifts it (`replacePin`), unless it brings new login data.
19
16
  */
20
17
  export class LoginGuard {
21
- refusal;
22
- /** The TAN method of the refused login; `undefined` without a refusal, and for new login data. */
23
- refusedMethodValue;
24
- /** Throws the bank's refusal of an earlier login, so that the PIN is not sent again. */
18
+ // ECMAScript private, so that neither `util.inspect` nor `JSON.stringify` of the client reaches it.
19
+ #pin;
20
+ refusalValue;
21
+ /** The TAN method of the login `refusalValue` refused, which a restored refusal does not tell. */
22
+ refusedMethod;
23
+ /** Logins sent since the bank last took the PIN whose answer the client has not read. */
24
+ unreadLogins = 0;
25
+ /** The refusal of `maxUnreadLogins` unread logins, kept so that `check` throws the same error each time. */
26
+ unreadLoginsRefusal;
27
+ /** The reasons `remedied` has lifted a refusal for in the running call. */
28
+ pardonedReasons = new Set();
29
+ constructor(pin) {
30
+ this.#pin = pin;
31
+ }
32
+ /** The PIN to sign a message with; throws the refusal instead (`check`), and `FinTsStateError` once closed. */
33
+ get pin() {
34
+ this.check();
35
+ if (this.#pin === undefined)
36
+ throw clientClosedError();
37
+ return this.#pin;
38
+ }
39
+ /** Throws the refusal, if there is one. */
25
40
  check() {
26
- if (this.refusal !== undefined)
27
- throw this.refusal;
41
+ const refusal = this.refusal;
42
+ if (refusal !== undefined)
43
+ throw refusal;
28
44
  }
29
- /** Whether the guard holds a refusal. */
30
- get hasRefusal() {
31
- return this.refusal !== undefined;
45
+ /** The bank's refusal, else that of `maxUnreadLogins` unread logins; `undefined` while there is none. */
46
+ get refusal() {
47
+ if (this.refusalValue !== undefined || this.unreadLogins < maxUnreadLogins)
48
+ return this.refusalValue;
49
+ this.unreadLoginsRefusal ??= new FinTsLoginRefusedError([], `The answers to the last ${maxUnreadLogins} logins were lost or unreadable, and the bank may have counted them ` +
50
+ 'as failed attempts');
51
+ return this.unreadLoginsRefusal;
32
52
  }
33
- /** The TAN method the refused login was signed with; `undefined` where the guard holds no refusal of a login. */
34
- get refusedMethod() {
35
- return this.refusedMethodValue;
53
+ /** Counts a login signed with the PIN as unread until `judge` reads its answer. */
54
+ loginSent() {
55
+ this.unreadLogins++;
36
56
  }
37
57
  /**
38
- * Whether `error`, raised for the answer to a request of `kind` in a dialog signed with `method`, refuses the login;
39
- * records it if it does.
40
- *
41
- * `dialogOpen` tells whether the bank keeps the dialog open after its answer.
58
+ * Judges `error`, if the answer to a request of `kind` in a dialog signed with `method` has one: returns the refusal
59
+ * of the login it amounts to, and records it unless the guard holds one already. Any error refuses a login; after the
60
+ * login only one that refuses it itself, a rejected PIN or new login data, since the bank checked the PIN before.
42
61
  */
43
- judge(error, kind, dialogOpen, method) {
44
- const refused = error instanceof FinTsPinError || kind === 'login' || (kind === 'loginWithOrder' && !dialogOpen);
45
- if (refused && this.refusal === undefined) {
46
- this.refusal = error;
47
- this.refusedMethodValue = method;
62
+ judge(error, kind, method) {
63
+ const refused = error instanceof FinTsLoginRefusedError || (error !== undefined && kind === 'login');
64
+ if (kind === 'login')
65
+ this.unreadLogins = refused ? Math.max(0, this.unreadLogins - 1) : 0;
66
+ if (!refused)
67
+ return undefined;
68
+ const refusal = loginRefusalFor(error.feedback);
69
+ if (this.refusalValue === undefined) {
70
+ this.refusalValue = refusal;
71
+ this.refusedMethod = method;
48
72
  }
49
- return refused;
73
+ return refusal;
50
74
  }
51
- /** Records `error` as the reason the client logs in no more, unless it holds a refusal already. */
52
- refuse(error) {
53
- this.refusal ??= error;
54
- }
55
- /**
56
- * Runs `lookup`, which only completes data, such as the SEPA account list; `undefined` where the bank rejects it, so
57
- * that the caller carries on without it.
58
- *
59
- * A refused login is thrown, and so is a wrong TAN, which the user must hear of. An aborted dialog counts as a
60
- * rejection: banks end the dialog along with many.
61
- */
62
- async tryLookUp(lookup) {
75
+ /** Runs `call`, one call of the client; a refusal that outlasts it is final, and the next call remedies anew. */
76
+ async runCall(call) {
63
77
  try {
64
- return await lookup();
78
+ return await call();
65
79
  }
66
- catch (error) {
67
- if (!(error instanceof FinTsBankError) || this.isRefusal(error) || error instanceof FinTsTanError)
68
- throw error;
69
- return undefined;
80
+ finally {
81
+ this.pardonedReasons.clear();
82
+ this.forgetPinIfRefused();
70
83
  }
71
84
  }
72
85
  /**
73
- * The reasons the session can take care of in `error`: 3920 without the TAN method of the refused login, 9075 after
74
- * the one-step method, and 9391. Empty if `error` is not the refusal the guard holds, rejects the PIN, or brings new
75
- * login data.
76
- *
77
- * Only the return codes of the refusal itself count: TAN methods listed earlier say nothing about this login.
86
+ * Runs `attempt`, which may log in, and runs it once more each time `remedy` takes care of a reason the refusal it
87
+ * failed with names; `remedy` gets the reason and the TAN method of the refused login, and returns whether it could.
78
88
  */
79
- remediableReasons(error) {
80
- const method = this.refusedMethodValue;
81
- if (!this.isRefusal(error) ||
82
- method === undefined ||
83
- error instanceof FinTsPinError ||
84
- error instanceof FinTsLoginDataChangedError) {
85
- return [];
89
+ async remedied(attempt, remedy) {
90
+ // A refusal of an earlier call stops the attempt before it sends the PIN; there is nothing to remedy.
91
+ if (this.refusal !== undefined)
92
+ return attempt();
93
+ for (;;) {
94
+ try {
95
+ return await attempt();
96
+ }
97
+ catch (error) {
98
+ const refusal = this.refusalValue;
99
+ const rejected = this.refusedMethod;
100
+ // Only the bank's refusal the guard holds names reasons; any other error, a second refusal too, fails the call.
101
+ if (refusal === undefined || error !== refusal || rejected === undefined)
102
+ throw error;
103
+ const reason = remediableReasons(refusal, rejected).find((r) => !this.pardonedReasons.has(r));
104
+ if (reason === undefined)
105
+ throw error;
106
+ this.pardonedReasons.add(reason);
107
+ // The refusal named a reason the session takes care of, not the PIN, so it does not outlast a remedy that fails,
108
+ // such as a TAN method prompt the app ends: the call fails with the remedy's own error. On a closed client the
109
+ // next attempt fails before it sends anything.
110
+ const remedied = await remedy(reason, rejected).catch((remedyError) => {
111
+ this.liftRefusal();
112
+ throw remedyError;
113
+ });
114
+ if (!remedied)
115
+ throw error;
116
+ this.liftRefusal();
117
+ }
86
118
  }
87
- const feedback = error.feedback;
88
- const allowed = allowedTanMethodsIn(feedback);
89
- const reasons = [];
90
- if ((allowed.length > 0 && !allowed.includes(method.securityFunction)) ||
91
- (method.isOneStep && hasCode(feedback, FeedbackCodes.scaRequired))) {
92
- reasons.push('tanMethodNotAllowed');
119
+ }
120
+ /**
121
+ * Signs with `pin` from now on, a PIN the user has entered anew and the protocol can send, and lifts the refusal.
122
+ * Throws the refusal instead where it brings new login data, which end the old ones for good.
123
+ */
124
+ replacePin(pin) {
125
+ if (this.refusalValue instanceof FinTsLoginDataChangedError)
126
+ throw this.refusalValue;
127
+ this.#pin = pin;
128
+ this.liftRefusal();
129
+ this.unreadLogins = 0;
130
+ this.unreadLoginsRefusal = undefined;
131
+ }
132
+ /** Forgets the PIN; the client calls it once it is closed. */
133
+ close() {
134
+ this.#pin = undefined;
135
+ }
136
+ saveState() {
137
+ return { refusal: this.refusalValue?.feedback, unreadLogins: this.unreadLogins };
138
+ }
139
+ /** Restores what `saveState` returned; a restored refusal is final, also one without return codes. */
140
+ restoreState(saved) {
141
+ if (saved.refusal !== undefined) {
142
+ this.refusalValue =
143
+ saved.refusal.length > 0
144
+ ? loginRefusalFor(saved.refusal)
145
+ : new FinTsLoginRefusedError([], 'A login was refused, and the saved state no longer tells why');
93
146
  }
94
- if (hasCode(feedback, FeedbackCodes.newSystemIdRequired))
95
- reasons.push('systemIdRejected');
96
- return reasons;
147
+ this.unreadLogins = Math.max(0, saved.unreadLogins);
148
+ this.forgetPinIfRefused();
97
149
  }
98
- /** Lifts `refusal` once the session has taken care of its reason, so that the next login may send the PIN. */
99
- pardon(refusal) {
100
- if (!this.isRefusal(refusal))
101
- return;
102
- this.refusal = undefined;
103
- this.refusedMethodValue = undefined;
150
+ liftRefusal() {
151
+ this.refusalValue = undefined;
152
+ this.refusedMethod = undefined;
104
153
  }
105
- isRefusal(error) {
106
- return error === this.refusal;
154
+ forgetPinIfRefused() {
155
+ if (this.refusal !== undefined)
156
+ this.#pin = undefined;
107
157
  }
108
158
  }
159
+ /**
160
+ * The reasons `refusal`, of a login signed with `method`, names that the session can take care of: a TAN method the
161
+ * refusal names as not allowed (`refusesMethod`), and 9391. None for a rejected PIN or new login data.
162
+ */
163
+ function remediableReasons(refusal, method) {
164
+ if (refusal instanceof FinTsPinError || refusal instanceof FinTsLoginDataChangedError)
165
+ return [];
166
+ const reasons = [];
167
+ if (refusesMethod(refusal.feedback, method))
168
+ reasons.push('tanMethodNotAllowed');
169
+ if (hasCode(refusal.feedback, FeedbackCodes.newSystemIdRequired))
170
+ reasons.push('systemIdRejected');
171
+ return reasons;
172
+ }
173
+ /**
174
+ * Error codes that cannot be about the PIN: 9050 (the message contains errors) and 9800, which only say that the
175
+ * message failed, and 9955 (the one-step method is not allowed).
176
+ */
177
+ const errorsNotAboutThePin = new Set(['9050', FeedbackCodes.dialogAborted, '9955']);
178
+ /**
179
+ * Whether `feedback`, which refuses a login signed with `method`, names the method as the reason: a 3920 that lists the
180
+ * methods the user may use without it, or 9075 after the one-step method.
181
+ *
182
+ * Only the return codes of the refusal itself count: TAN methods listed earlier say nothing about this login. Banks
183
+ * list two-step methods only, so a 3920 always leaves the one-step method out, also where the login succeeds. After
184
+ * the one-step method a 3920 counts only where the bank names no other error than one not about the PIN: any other,
185
+ * such as "Anmeldung fehlgeschlagen", may be about it. PIN/TAN B.8.2 has the bank answer a one-step dialog of a user
186
+ * bound to two-step methods with 9955 and 3920.
187
+ */
188
+ function refusesMethod(feedback, method) {
189
+ if (method.isOneStep && hasCode(feedback, FeedbackCodes.scaRequired))
190
+ return true;
191
+ const allowed = allowedTanMethodsIn(feedback);
192
+ if (allowed.length === 0 || allowed.includes(method.securityFunction))
193
+ return false;
194
+ return !method.isOneStep || feedback.every((f) => !f.isError || errorsNotAboutThePin.has(f.code));
195
+ }
109
196
  //# sourceMappingURL=login-guard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"login-guard.js","sourceRoot":"","sources":["../../src/session/login-guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"login-guard.js","sourceRoot":"","sources":["../../src/session/login-guard.ts"],"names":[],"mappings":"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"}