@ktmcp-cli/nordigen 1.0.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.
- package/.env.example +11 -0
- package/.eslintrc.json +17 -0
- package/AGENT.md +480 -0
- package/CHANGELOG.md +69 -0
- package/CONTRIBUTING.md +198 -0
- package/EXAMPLES.md +561 -0
- package/INDEX.md +193 -0
- package/LICENSE +21 -0
- package/OPENCLAW.md +468 -0
- package/PROJECT.md +366 -0
- package/QUICKREF.md +231 -0
- package/README.md +424 -0
- package/SETUP.md +259 -0
- package/SUMMARY.md +419 -0
- package/banner.png +0 -0
- package/bin/nordigen.js +84 -0
- package/logo.png +0 -0
- package/package.json +40 -0
- package/scripts/quickstart.sh +110 -0
- package/src/commands/accounts.js +205 -0
- package/src/commands/agreements.js +241 -0
- package/src/commands/auth.js +86 -0
- package/src/commands/config.js +173 -0
- package/src/commands/institutions.js +181 -0
- package/src/commands/payments.js +228 -0
- package/src/commands/requisitions.js +239 -0
- package/src/lib/api.js +491 -0
- package/src/lib/auth.js +113 -0
- package/src/lib/config.js +145 -0
- package/src/lib/output.js +255 -0
- package/test/api.test.js +88 -0
package/src/lib/api.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nordigen API Client
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Core API client for interacting with Nordigen endpoints
|
|
5
|
+
* @module lib/api
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fetch from 'node-fetch';
|
|
9
|
+
import { getConfig } from './config.js';
|
|
10
|
+
|
|
11
|
+
const BASE_URL = 'https://ob.nordigen.com';
|
|
12
|
+
const API_VERSION = 'v2';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* API Client for Nordigen
|
|
16
|
+
*/
|
|
17
|
+
export class NordigenAPI {
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} [accessToken] - JWT access token
|
|
20
|
+
*/
|
|
21
|
+
constructor(accessToken = null) {
|
|
22
|
+
this.baseURL = BASE_URL;
|
|
23
|
+
this.accessToken = accessToken || this.getStoredToken();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get stored access token from config
|
|
28
|
+
* @returns {string|null}
|
|
29
|
+
*/
|
|
30
|
+
getStoredToken() {
|
|
31
|
+
const config = getConfig();
|
|
32
|
+
return config.get('auth.access_token') || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Make an HTTP request to the API
|
|
37
|
+
*
|
|
38
|
+
* @param {string} method - HTTP method
|
|
39
|
+
* @param {string} endpoint - API endpoint (without base URL)
|
|
40
|
+
* @param {Object} [options={}] - Request options
|
|
41
|
+
* @param {Object} [options.body] - Request body
|
|
42
|
+
* @param {Object} [options.query] - Query parameters
|
|
43
|
+
* @param {boolean} [options.auth=true] - Whether to include auth header
|
|
44
|
+
* @returns {Promise<Object>}
|
|
45
|
+
* @throws {Error} API errors with status codes
|
|
46
|
+
*/
|
|
47
|
+
async request(method, endpoint, options = {}) {
|
|
48
|
+
const { body, query, auth = true } = options;
|
|
49
|
+
|
|
50
|
+
// Build URL with query parameters
|
|
51
|
+
const url = new URL(`${this.baseURL}${endpoint}`);
|
|
52
|
+
if (query) {
|
|
53
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
54
|
+
if (value !== undefined && value !== null) {
|
|
55
|
+
url.searchParams.append(key, value);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Build headers
|
|
61
|
+
const headers = {
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
'Accept': 'application/json',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (auth && this.accessToken) {
|
|
67
|
+
headers['Authorization'] = `Bearer ${this.accessToken}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Build request options
|
|
71
|
+
const fetchOptions = {
|
|
72
|
+
method,
|
|
73
|
+
headers,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (body) {
|
|
77
|
+
fetchOptions.body = JSON.stringify(body);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Make request
|
|
81
|
+
const response = await fetch(url.toString(), fetchOptions);
|
|
82
|
+
|
|
83
|
+
// Handle response
|
|
84
|
+
const contentType = response.headers.get('content-type');
|
|
85
|
+
let data;
|
|
86
|
+
|
|
87
|
+
if (contentType && contentType.includes('application/json')) {
|
|
88
|
+
data = await response.json();
|
|
89
|
+
} else {
|
|
90
|
+
data = await response.text();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Handle errors
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const error = new Error(data.summary || data.detail || `HTTP ${response.status}: ${response.statusText}`);
|
|
96
|
+
error.status = response.status;
|
|
97
|
+
error.statusCode = data.status_code || response.status;
|
|
98
|
+
error.detail = data.detail;
|
|
99
|
+
error.summary = data.summary;
|
|
100
|
+
error.type = data.type;
|
|
101
|
+
error.response = data;
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return data;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* GET request
|
|
110
|
+
* @param {string} endpoint - API endpoint
|
|
111
|
+
* @param {Object} [options] - Request options
|
|
112
|
+
* @returns {Promise<Object>}
|
|
113
|
+
*/
|
|
114
|
+
async get(endpoint, options = {}) {
|
|
115
|
+
return this.request('GET', endpoint, options);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* POST request
|
|
120
|
+
* @param {string} endpoint - API endpoint
|
|
121
|
+
* @param {Object} [body] - Request body
|
|
122
|
+
* @param {Object} [options] - Additional options
|
|
123
|
+
* @returns {Promise<Object>}
|
|
124
|
+
*/
|
|
125
|
+
async post(endpoint, body = {}, options = {}) {
|
|
126
|
+
return this.request('POST', endpoint, { ...options, body });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* PUT request
|
|
131
|
+
* @param {string} endpoint - API endpoint
|
|
132
|
+
* @param {Object} [body] - Request body
|
|
133
|
+
* @param {Object} [options] - Additional options
|
|
134
|
+
* @returns {Promise<Object>}
|
|
135
|
+
*/
|
|
136
|
+
async put(endpoint, body = {}, options = {}) {
|
|
137
|
+
return this.request('PUT', endpoint, { ...options, body });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* DELETE request
|
|
142
|
+
* @param {string} endpoint - API endpoint
|
|
143
|
+
* @param {Object} [options] - Request options
|
|
144
|
+
* @returns {Promise<Object>}
|
|
145
|
+
*/
|
|
146
|
+
async delete(endpoint, options = {}) {
|
|
147
|
+
return this.request('DELETE', endpoint, options);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ==================== Authentication ====================
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Obtain JWT access and refresh tokens
|
|
154
|
+
*
|
|
155
|
+
* @param {string} secretId - Secret ID
|
|
156
|
+
* @param {string} secretKey - Secret Key
|
|
157
|
+
* @returns {Promise<{access: string, refresh: string, access_expires: number, refresh_expires: number}>}
|
|
158
|
+
*/
|
|
159
|
+
async obtainToken(secretId, secretKey) {
|
|
160
|
+
return this.post('/api/v2/token/new/', {
|
|
161
|
+
secret_id: secretId,
|
|
162
|
+
secret_key: secretKey,
|
|
163
|
+
}, { auth: false });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Refresh JWT access token
|
|
168
|
+
*
|
|
169
|
+
* @param {string} refreshToken - Refresh token
|
|
170
|
+
* @returns {Promise<{access: string, access_expires: number}>}
|
|
171
|
+
*/
|
|
172
|
+
async refreshToken(refreshToken) {
|
|
173
|
+
return this.post('/api/v2/token/refresh/', {
|
|
174
|
+
refresh: refreshToken,
|
|
175
|
+
}, { auth: false });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ==================== Accounts ====================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get account metadata
|
|
182
|
+
*
|
|
183
|
+
* @param {string} accountId - Account UUID
|
|
184
|
+
* @returns {Promise<Object>}
|
|
185
|
+
*/
|
|
186
|
+
async getAccount(accountId) {
|
|
187
|
+
return this.get(`/api/v2/accounts/${accountId}/`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get account balances
|
|
192
|
+
*
|
|
193
|
+
* @param {string} accountId - Account UUID
|
|
194
|
+
* @returns {Promise<Object>}
|
|
195
|
+
*/
|
|
196
|
+
async getAccountBalances(accountId) {
|
|
197
|
+
return this.get(`/api/v2/accounts/${accountId}/balances/`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get account details
|
|
202
|
+
*
|
|
203
|
+
* @param {string} accountId - Account UUID
|
|
204
|
+
* @returns {Promise<Object>}
|
|
205
|
+
*/
|
|
206
|
+
async getAccountDetails(accountId) {
|
|
207
|
+
return this.get(`/api/v2/accounts/${accountId}/details/`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get account transactions
|
|
212
|
+
*
|
|
213
|
+
* @param {string} accountId - Account UUID
|
|
214
|
+
* @param {Object} [params] - Query parameters
|
|
215
|
+
* @param {string} [params.date_from] - Start date (YYYY-MM-DD)
|
|
216
|
+
* @param {string} [params.date_to] - End date (YYYY-MM-DD)
|
|
217
|
+
* @returns {Promise<Object>}
|
|
218
|
+
*/
|
|
219
|
+
async getAccountTransactions(accountId, params = {}) {
|
|
220
|
+
return this.get(`/api/v2/accounts/${accountId}/transactions/`, { query: params });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get premium account transactions
|
|
225
|
+
*
|
|
226
|
+
* @param {string} accountId - Account UUID
|
|
227
|
+
* @param {string} country - ISO 3166 country code
|
|
228
|
+
* @param {Object} [params] - Query parameters
|
|
229
|
+
* @param {string} [params.date_from] - Start date (YYYY-MM-DD)
|
|
230
|
+
* @param {string} [params.date_to] - End date (YYYY-MM-DD)
|
|
231
|
+
* @returns {Promise<Object>}
|
|
232
|
+
*/
|
|
233
|
+
async getPremiumTransactions(accountId, country, params = {}) {
|
|
234
|
+
return this.get(`/api/v2/accounts/premium/${accountId}/transactions/`, {
|
|
235
|
+
query: { country, ...params }
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ==================== Institutions ====================
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* List all supported institutions
|
|
243
|
+
*
|
|
244
|
+
* @param {string} country - ISO 3166 country code
|
|
245
|
+
* @param {Object} [params] - Query parameters
|
|
246
|
+
* @param {boolean} [params.payments_enabled] - Filter by payment support
|
|
247
|
+
* @param {boolean} [params.account_selection] - Filter by account selection
|
|
248
|
+
* @returns {Promise<Array>}
|
|
249
|
+
*/
|
|
250
|
+
async listInstitutions(country, params = {}) {
|
|
251
|
+
return this.get('/api/v2/institutions/', {
|
|
252
|
+
query: { country, ...params }
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get institution details
|
|
258
|
+
*
|
|
259
|
+
* @param {string} institutionId - Institution ID
|
|
260
|
+
* @returns {Promise<Object>}
|
|
261
|
+
*/
|
|
262
|
+
async getInstitution(institutionId) {
|
|
263
|
+
return this.get(`/api/v2/institutions/${institutionId}/`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ==================== End User Agreements ====================
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* List all end user agreements
|
|
270
|
+
*
|
|
271
|
+
* @param {Object} [params] - Query parameters
|
|
272
|
+
* @param {number} [params.limit] - Results per page
|
|
273
|
+
* @param {number} [params.offset] - Offset for pagination
|
|
274
|
+
* @returns {Promise<Object>}
|
|
275
|
+
*/
|
|
276
|
+
async listAgreements(params = {}) {
|
|
277
|
+
return this.get('/api/v2/agreements/enduser/', { query: params });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Create end user agreement
|
|
282
|
+
*
|
|
283
|
+
* @param {Object} data - Agreement data
|
|
284
|
+
* @param {string} data.institution_id - Institution ID
|
|
285
|
+
* @param {number} [data.max_historical_days=90] - Max historical days
|
|
286
|
+
* @param {number} [data.access_valid_for_days=90] - Access validity days
|
|
287
|
+
* @param {Array<string>} [data.access_scope] - Access scopes
|
|
288
|
+
* @returns {Promise<Object>}
|
|
289
|
+
*/
|
|
290
|
+
async createAgreement(data) {
|
|
291
|
+
return this.post('/api/v2/agreements/enduser/', data);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Get end user agreement by ID
|
|
296
|
+
*
|
|
297
|
+
* @param {string} agreementId - Agreement UUID
|
|
298
|
+
* @returns {Promise<Object>}
|
|
299
|
+
*/
|
|
300
|
+
async getAgreement(agreementId) {
|
|
301
|
+
return this.get(`/api/v2/agreements/enduser/${agreementId}/`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Delete end user agreement
|
|
306
|
+
*
|
|
307
|
+
* @param {string} agreementId - Agreement UUID
|
|
308
|
+
* @returns {Promise<Object>}
|
|
309
|
+
*/
|
|
310
|
+
async deleteAgreement(agreementId) {
|
|
311
|
+
return this.delete(`/api/v2/agreements/enduser/${agreementId}/`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Accept end user agreement
|
|
316
|
+
*
|
|
317
|
+
* @param {string} agreementId - Agreement UUID
|
|
318
|
+
* @param {Object} data - Acceptance data
|
|
319
|
+
* @param {string} data.user_agent - User agent string
|
|
320
|
+
* @param {string} data.ip_address - User IP address
|
|
321
|
+
* @returns {Promise<Object>}
|
|
322
|
+
*/
|
|
323
|
+
async acceptAgreement(agreementId, data) {
|
|
324
|
+
return this.put(`/api/v2/agreements/enduser/${agreementId}/accept/`, data);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ==================== Requisitions ====================
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* List all requisitions
|
|
331
|
+
*
|
|
332
|
+
* @param {Object} [params] - Query parameters
|
|
333
|
+
* @param {number} [params.limit] - Results per page
|
|
334
|
+
* @param {number} [params.offset] - Offset for pagination
|
|
335
|
+
* @returns {Promise<Object>}
|
|
336
|
+
*/
|
|
337
|
+
async listRequisitions(params = {}) {
|
|
338
|
+
return this.get('/api/v2/requisitions/', { query: params });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Create requisition
|
|
343
|
+
*
|
|
344
|
+
* @param {Object} data - Requisition data
|
|
345
|
+
* @param {string} data.redirect - Redirect URL after auth
|
|
346
|
+
* @param {string} data.institution_id - Institution ID
|
|
347
|
+
* @param {string} [data.reference] - Custom reference
|
|
348
|
+
* @param {string} [data.agreement] - Agreement UUID
|
|
349
|
+
* @param {string} [data.user_language] - User language code
|
|
350
|
+
* @param {boolean} [data.account_selection] - Enable account selection
|
|
351
|
+
* @param {boolean} [data.redirect_immediate] - Redirect immediately
|
|
352
|
+
* @returns {Promise<Object>}
|
|
353
|
+
*/
|
|
354
|
+
async createRequisition(data) {
|
|
355
|
+
return this.post('/api/v2/requisitions/', data);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get requisition by ID
|
|
360
|
+
*
|
|
361
|
+
* @param {string} requisitionId - Requisition UUID
|
|
362
|
+
* @returns {Promise<Object>}
|
|
363
|
+
*/
|
|
364
|
+
async getRequisition(requisitionId) {
|
|
365
|
+
return this.get(`/api/v2/requisitions/${requisitionId}/`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Delete requisition
|
|
370
|
+
*
|
|
371
|
+
* @param {string} requisitionId - Requisition UUID
|
|
372
|
+
* @returns {Promise<Object>}
|
|
373
|
+
*/
|
|
374
|
+
async deleteRequisition(requisitionId) {
|
|
375
|
+
return this.delete(`/api/v2/requisitions/${requisitionId}/`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ==================== Payments ====================
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* List payments
|
|
382
|
+
*
|
|
383
|
+
* @param {Object} [params] - Query parameters
|
|
384
|
+
* @param {number} [params.limit] - Results per page
|
|
385
|
+
* @param {number} [params.offset] - Offset for pagination
|
|
386
|
+
* @returns {Promise<Object>}
|
|
387
|
+
*/
|
|
388
|
+
async listPayments(params = {}) {
|
|
389
|
+
return this.get('/api/v2/payments/', { query: params });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Create payment
|
|
394
|
+
*
|
|
395
|
+
* @param {Object} data - Payment data
|
|
396
|
+
* @returns {Promise<Object>}
|
|
397
|
+
*/
|
|
398
|
+
async createPayment(data) {
|
|
399
|
+
return this.post('/api/v2/payments/', data);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get payment by ID
|
|
404
|
+
*
|
|
405
|
+
* @param {string} paymentId - Payment UUID
|
|
406
|
+
* @returns {Promise<Object>}
|
|
407
|
+
*/
|
|
408
|
+
async getPayment(paymentId) {
|
|
409
|
+
return this.get(`/api/v2/payments/${paymentId}/`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Delete periodic payment
|
|
414
|
+
*
|
|
415
|
+
* @param {string} paymentId - Payment UUID
|
|
416
|
+
* @returns {Promise<Object>}
|
|
417
|
+
*/
|
|
418
|
+
async deletePayment(paymentId) {
|
|
419
|
+
return this.delete(`/api/v2/payments/${paymentId}/`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* List payment creditor accounts
|
|
424
|
+
*
|
|
425
|
+
* @param {Object} [params] - Query parameters
|
|
426
|
+
* @returns {Promise<Object>}
|
|
427
|
+
*/
|
|
428
|
+
async listCreditorAccounts(params = {}) {
|
|
429
|
+
return this.get('/api/v2/payments/account/', { query: params });
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* List payment creditors
|
|
434
|
+
*
|
|
435
|
+
* @param {Object} [params] - Query parameters
|
|
436
|
+
* @returns {Promise<Object>}
|
|
437
|
+
*/
|
|
438
|
+
async listCreditors(params = {}) {
|
|
439
|
+
return this.get('/api/v2/payments/creditors/', { query: params });
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Create payment creditor
|
|
444
|
+
*
|
|
445
|
+
* @param {Object} data - Creditor data
|
|
446
|
+
* @returns {Promise<Object>}
|
|
447
|
+
*/
|
|
448
|
+
async createCreditor(data) {
|
|
449
|
+
return this.post('/api/v2/payments/creditors/', data);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get payment creditor
|
|
454
|
+
*
|
|
455
|
+
* @param {string} creditorId - Creditor UUID
|
|
456
|
+
* @returns {Promise<Object>}
|
|
457
|
+
*/
|
|
458
|
+
async getCreditor(creditorId) {
|
|
459
|
+
return this.get(`/api/v2/payments/creditors/${creditorId}/`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Delete payment creditor
|
|
464
|
+
*
|
|
465
|
+
* @param {string} creditorId - Creditor UUID
|
|
466
|
+
* @returns {Promise<Object>}
|
|
467
|
+
*/
|
|
468
|
+
async deleteCreditor(creditorId) {
|
|
469
|
+
return this.delete(`/api/v2/payments/creditors/${creditorId}/`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get minimum required fields for institution payments
|
|
474
|
+
*
|
|
475
|
+
* @param {string} institutionId - Institution ID
|
|
476
|
+
* @returns {Promise<Object>}
|
|
477
|
+
*/
|
|
478
|
+
async getPaymentFields(institutionId) {
|
|
479
|
+
return this.get(`/api/v2/payments/fields/${institutionId}/`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Create API client instance
|
|
485
|
+
*
|
|
486
|
+
* @param {string} [accessToken] - JWT access token
|
|
487
|
+
* @returns {NordigenAPI}
|
|
488
|
+
*/
|
|
489
|
+
export function createClient(accessToken = null) {
|
|
490
|
+
return new NordigenAPI(accessToken);
|
|
491
|
+
}
|
package/src/lib/auth.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Management
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Handles JWT token authentication and refresh
|
|
5
|
+
* @module lib/auth
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getConfig, isAccessTokenValid, isRefreshTokenValid } from './config.js';
|
|
9
|
+
import { createClient } from './api.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Ensure valid access token, refreshing if necessary
|
|
13
|
+
*
|
|
14
|
+
* @returns {Promise<string>} Valid access token
|
|
15
|
+
* @throws {Error} If authentication fails
|
|
16
|
+
*/
|
|
17
|
+
export async function ensureAuth() {
|
|
18
|
+
const config = getConfig();
|
|
19
|
+
|
|
20
|
+
// Check if access token is valid
|
|
21
|
+
if (isAccessTokenValid()) {
|
|
22
|
+
return config.get('auth.access_token');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Try to refresh token
|
|
26
|
+
if (isRefreshTokenValid()) {
|
|
27
|
+
const refreshToken = config.get('auth.refresh_token');
|
|
28
|
+
const api = createClient();
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const result = await api.refreshToken(refreshToken);
|
|
32
|
+
|
|
33
|
+
// Save new access token
|
|
34
|
+
const now = Math.floor(Date.now() / 1000);
|
|
35
|
+
config.set('auth.access_token', result.access);
|
|
36
|
+
config.set('auth.access_expires', now + result.access_expires);
|
|
37
|
+
|
|
38
|
+
return result.access;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error('Failed to refresh token. Please login again using: nordigen auth login');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// No valid tokens
|
|
45
|
+
throw new Error('Not authenticated. Please login using: nordigen auth login --secret-id <id> --secret-key <key>');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Login with secret credentials
|
|
50
|
+
*
|
|
51
|
+
* @param {string} secretId - Secret ID
|
|
52
|
+
* @param {string} secretKey - Secret Key
|
|
53
|
+
* @returns {Promise<Object>} Token response
|
|
54
|
+
*/
|
|
55
|
+
export async function login(secretId, secretKey) {
|
|
56
|
+
const api = createClient();
|
|
57
|
+
const result = await api.obtainToken(secretId, secretKey);
|
|
58
|
+
|
|
59
|
+
// Save credentials and tokens
|
|
60
|
+
const config = getConfig();
|
|
61
|
+
const now = Math.floor(Date.now() / 1000);
|
|
62
|
+
|
|
63
|
+
config.set('auth.secret_id', secretId);
|
|
64
|
+
config.set('auth.secret_key', secretKey);
|
|
65
|
+
config.set('auth.access_token', result.access);
|
|
66
|
+
config.set('auth.refresh_token', result.refresh);
|
|
67
|
+
config.set('auth.access_expires', now + result.access_expires);
|
|
68
|
+
config.set('auth.refresh_expires', now + result.refresh_expires);
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Logout (clear credentials)
|
|
75
|
+
*/
|
|
76
|
+
export function logout() {
|
|
77
|
+
const config = getConfig();
|
|
78
|
+
config.delete('auth');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if authenticated
|
|
83
|
+
*
|
|
84
|
+
* @returns {boolean}
|
|
85
|
+
*/
|
|
86
|
+
export function isAuthenticated() {
|
|
87
|
+
return isAccessTokenValid() || isRefreshTokenValid();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get authentication status
|
|
92
|
+
*
|
|
93
|
+
* @returns {Object} Authentication status details
|
|
94
|
+
*/
|
|
95
|
+
export function getAuthStatus() {
|
|
96
|
+
const config = getConfig();
|
|
97
|
+
const accessToken = config.get('auth.access_token');
|
|
98
|
+
const refreshToken = config.get('auth.refresh_token');
|
|
99
|
+
const accessExpires = config.get('auth.access_expires');
|
|
100
|
+
const refreshExpires = config.get('auth.refresh_expires');
|
|
101
|
+
|
|
102
|
+
const now = Math.floor(Date.now() / 1000);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
authenticated: isAuthenticated(),
|
|
106
|
+
hasAccessToken: !!accessToken,
|
|
107
|
+
hasRefreshToken: !!refreshToken,
|
|
108
|
+
accessTokenValid: isAccessTokenValid(),
|
|
109
|
+
refreshTokenValid: isRefreshTokenValid(),
|
|
110
|
+
accessExpiresIn: accessExpires ? Math.max(0, accessExpires - now) : 0,
|
|
111
|
+
refreshExpiresIn: refreshExpires ? Math.max(0, refreshExpires - now) : 0,
|
|
112
|
+
};
|
|
113
|
+
}
|