@actual-app/sync-server 26.10.0 → 26.11.0-nightly.20261004
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/build/app.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as run } from "./chunks/migrations-7U65Wxd5.js";
|
|
2
2
|
//#region app.ts
|
|
3
3
|
run().then(() => {
|
|
4
|
-
import("./chunks/app-
|
|
4
|
+
import("./chunks/app-C9cIy7Ri.js").then((app) => app.run());
|
|
5
5
|
}).catch((err) => {
|
|
6
6
|
console.log("Error starting app:", err);
|
|
7
7
|
process.exit(1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actual-server.js","names":[],"sources":["../../bin/actual-server.js"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nconst args = process.argv;\n\nconst options = {\n help: {\n type: 'boolean',\n short: 'h',\n },\n version: {\n type: 'boolean',\n short: 'v',\n },\n 'reset-password': {\n type: 'boolean',\n },\n config: {\n type: 'string',\n },\n};\n\nconst { values } = parseArgs({\n args,\n options,\n allowPositionals: true,\n});\n\nif (values.help) {\n console.log(\n [\n 'usage: actual-server [options]',\n '',\n 'options:',\n ' --config Path to config file',\n '',\n ' -h --help Print this list and exit.',\n ' -v --version Print the version and exit.',\n '',\n 'Examples:',\n '',\n 'Runs actual-server with default configuration',\n ' actual-server',\n '',\n 'Runs actual-server with custom configuration',\n ' actual-server --config ./config.json',\n ].join('\\n'),\n );\n\n process.exit();\n}\n\nif (values.version) {\n console.log('v' + __APP_VERSION__);\n process.exit();\n}\n\nconst setupDataDir = dataDir => {\n if (process.env.ACTUAL_DATA_DIR) {\n return; // Env variables must not be overwritten\n }\n\n if (dataDir) {\n process.env.ACTUAL_DATA_DIR = dataDir; // Use the dir specified\n } else {\n // Setup defaults\n if (existsSync('./data')) {\n // The default data directory exists - use it\n console.info('Found existing data directory');\n process.env.ACTUAL_DATA_DIR = resolve('./data');\n } else {\n console.info(\n 'Using default data directory. You can specify a custom config with --config',\n );\n process.env.ACTUAL_DATA_DIR = resolve('./');\n }\n\n console.info(`Data directory: ${process.env.ACTUAL_DATA_DIR}`);\n }\n};\n\nif (values.config) {\n const configExists = existsSync(values.config);\n\n if (!configExists) {\n console.log(\n `Please specify a valid config path. The path ${String(values.config)} does not exist.`,\n );\n\n process.exit();\n } else {\n console.log(`Loading config from ${String(values.config)}`);\n const configJson = JSON.parse(readFileSync(values.config, 'utf-8'));\n process.env.ACTUAL_CONFIG_PATH = values.config;\n setupDataDir(configJson.dataDir);\n }\n} else {\n // If no config is specified, check for a default config in the current directory\n const defaultConfigJsonFile = './config.json';\n const configExists = existsSync(defaultConfigJsonFile);\n\n if (configExists) {\n console.info('Found config.json in the current directory');\n const configJson = JSON.parse(readFileSync(defaultConfigJsonFile, 'utf-8'));\n process.env.ACTUAL_CONFIG_PATH = defaultConfigJsonFile;\n setupDataDir(configJson.dataDir);\n } else {\n setupDataDir(); // No default config exists - setup data dir with defaults\n }\n}\n\nif (values['reset-password']) {\n console.info('Running reset password script...');\n await import('../src/scripts/reset-password.js');\n process.exit();\n}\n\n// start the sync server\nvoid import('../app.js');\n"],"mappings":";;;;;AAKA,IAAM,OAAO,QAAQ;AAmBrB,IAAM,EAAE,WAAW,UAAU;CAC3B;CACA,SAAA;EAlBA,MAAM;GACJ,MAAM;GACN,OAAO;EACT;EACA,SAAS;GACP,MAAM;GACN,OAAO;EACT;EACA,kBAAkB,EAChB,MAAM,UACR;EACA,QAAQ,EACN,MAAM,SACR;CAKM;CACN,kBAAkB;AACpB,CAAC;AAED,IAAI,OAAO,MAAM;CACf,QAAQ,IACN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI,CACb;CAEA,QAAQ,KAAK;AACf;AAEA,IAAI,OAAO,SAAS;CAClB,QAAQ,IAAI,
|
|
1
|
+
{"version":3,"file":"actual-server.js","names":[],"sources":["../../bin/actual-server.js"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nconst args = process.argv;\n\nconst options = {\n help: {\n type: 'boolean',\n short: 'h',\n },\n version: {\n type: 'boolean',\n short: 'v',\n },\n 'reset-password': {\n type: 'boolean',\n },\n config: {\n type: 'string',\n },\n};\n\nconst { values } = parseArgs({\n args,\n options,\n allowPositionals: true,\n});\n\nif (values.help) {\n console.log(\n [\n 'usage: actual-server [options]',\n '',\n 'options:',\n ' --config Path to config file',\n '',\n ' -h --help Print this list and exit.',\n ' -v --version Print the version and exit.',\n '',\n 'Examples:',\n '',\n 'Runs actual-server with default configuration',\n ' actual-server',\n '',\n 'Runs actual-server with custom configuration',\n ' actual-server --config ./config.json',\n ].join('\\n'),\n );\n\n process.exit();\n}\n\nif (values.version) {\n console.log('v' + __APP_VERSION__);\n process.exit();\n}\n\nconst setupDataDir = dataDir => {\n if (process.env.ACTUAL_DATA_DIR) {\n return; // Env variables must not be overwritten\n }\n\n if (dataDir) {\n process.env.ACTUAL_DATA_DIR = dataDir; // Use the dir specified\n } else {\n // Setup defaults\n if (existsSync('./data')) {\n // The default data directory exists - use it\n console.info('Found existing data directory');\n process.env.ACTUAL_DATA_DIR = resolve('./data');\n } else {\n console.info(\n 'Using default data directory. You can specify a custom config with --config',\n );\n process.env.ACTUAL_DATA_DIR = resolve('./');\n }\n\n console.info(`Data directory: ${process.env.ACTUAL_DATA_DIR}`);\n }\n};\n\nif (values.config) {\n const configExists = existsSync(values.config);\n\n if (!configExists) {\n console.log(\n `Please specify a valid config path. The path ${String(values.config)} does not exist.`,\n );\n\n process.exit();\n } else {\n console.log(`Loading config from ${String(values.config)}`);\n const configJson = JSON.parse(readFileSync(values.config, 'utf-8'));\n process.env.ACTUAL_CONFIG_PATH = values.config;\n setupDataDir(configJson.dataDir);\n }\n} else {\n // If no config is specified, check for a default config in the current directory\n const defaultConfigJsonFile = './config.json';\n const configExists = existsSync(defaultConfigJsonFile);\n\n if (configExists) {\n console.info('Found config.json in the current directory');\n const configJson = JSON.parse(readFileSync(defaultConfigJsonFile, 'utf-8'));\n process.env.ACTUAL_CONFIG_PATH = defaultConfigJsonFile;\n setupDataDir(configJson.dataDir);\n } else {\n setupDataDir(); // No default config exists - setup data dir with defaults\n }\n}\n\nif (values['reset-password']) {\n console.info('Running reset password script...');\n await import('../src/scripts/reset-password.js');\n process.exit();\n}\n\n// start the sync server\nvoid import('../app.js');\n"],"mappings":";;;;;AAKA,IAAM,OAAO,QAAQ;AAmBrB,IAAM,EAAE,WAAW,UAAU;CAC3B;CACA,SAAA;EAlBA,MAAM;GACJ,MAAM;GACN,OAAO;EACT;EACA,SAAS;GACP,MAAM;GACN,OAAO;EACT;EACA,kBAAkB,EAChB,MAAM,UACR;EACA,QAAQ,EACN,MAAM,SACR;CAKM;CACN,kBAAkB;AACpB,CAAC;AAED,IAAI,OAAO,MAAM;CACf,QAAQ,IACN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI,CACb;CAEA,QAAQ,KAAK;AACf;AAEA,IAAI,OAAO,SAAS;CAClB,QAAQ,IAAI,2BAAqB;CACjC,QAAQ,KAAK;AACf;AAEA,IAAM,gBAAe,YAAW;CAC9B,IAAI,QAAQ,IAAI,iBACd;CAGF,IAAI,SACF,QAAQ,IAAI,kBAAkB;MACzB;EAEL,IAAI,WAAW,QAAQ,GAAG;GAExB,QAAQ,KAAK,+BAA+B;GAC5C,QAAQ,IAAI,kBAAkB,QAAQ,QAAQ;EAChD,OAAO;GACL,QAAQ,KACN,6EACF;GACA,QAAQ,IAAI,kBAAkB,QAAQ,IAAI;EAC5C;EAEA,QAAQ,KAAK,mBAAmB,QAAQ,IAAI,iBAAiB;CAC/D;AACF;AAEA,IAAI,OAAO,QAGT,IAAI,CAFiB,WAAW,OAAO,MAEvB,GAAG;CACjB,QAAQ,IACN,gDAAgD,OAAO,OAAO,MAAM,EAAE,iBACxE;CAEA,QAAQ,KAAK;AACf,OAAO;CACL,QAAQ,IAAI,uBAAuB,OAAO,OAAO,MAAM,GAAG;CAC1D,MAAM,aAAa,KAAK,MAAM,aAAa,OAAO,QAAQ,OAAO,CAAC;CAClE,QAAQ,IAAI,qBAAqB,OAAO;CACxC,aAAa,WAAW,OAAO;AACjC;KACK;CAEL,MAAM,wBAAwB;CAG9B,IAFqB,WAAW,qBAEjB,GAAG;EAChB,QAAQ,KAAK,4CAA4C;EACzD,MAAM,aAAa,KAAK,MAAM,aAAa,uBAAuB,OAAO,CAAC;EAC1E,QAAQ,IAAI,qBAAqB;EACjC,aAAa,WAAW,OAAO;CACjC,OACE,aAAa;AAEjB;AAEA,IAAI,OAAO,mBAAmB;CAC5B,QAAQ,KAAK,kCAAkC;CAC/C,MAAM,OAAO;CACb,QAAQ,KAAK;AACf;AAGK,OAAO"}
|
|
@@ -1280,7 +1280,7 @@ var enableBankingService = {
|
|
|
1280
1280
|
async getAspsps(country) {
|
|
1281
1281
|
return request("GET", `/aspsps${country ? `?country=${encodeURIComponent(country)}` : ""}`);
|
|
1282
1282
|
},
|
|
1283
|
-
async startAuth(aspsp, redirectUrl, state, maxConsentValidity, psuType = "personal") {
|
|
1283
|
+
async startAuth(aspsp, redirectUrl, state, maxConsentValidity, psuType = "personal", psuHeaders) {
|
|
1284
1284
|
const defaultMs = 2160 * 60 * 60 * 1e3;
|
|
1285
1285
|
const consentMs = maxConsentValidity != null && maxConsentValidity > 0 ? Math.min(maxConsentValidity * 1e3, defaultMs) : defaultMs;
|
|
1286
1286
|
const validUntil = new Date(Date.now() + consentMs);
|
|
@@ -1291,9 +1291,13 @@ var enableBankingService = {
|
|
|
1291
1291
|
},
|
|
1292
1292
|
redirect_url: redirectUrl,
|
|
1293
1293
|
state,
|
|
1294
|
-
access: {
|
|
1294
|
+
access: {
|
|
1295
|
+
valid_until: validUntil.toISOString(),
|
|
1296
|
+
balances: true,
|
|
1297
|
+
transactions: true
|
|
1298
|
+
},
|
|
1295
1299
|
psu_type: psuType
|
|
1296
|
-
});
|
|
1300
|
+
}, void 0, psuHeaders);
|
|
1297
1301
|
},
|
|
1298
1302
|
async createSession(code) {
|
|
1299
1303
|
return request("POST", "/sessions", { code });
|
|
@@ -1316,8 +1320,8 @@ var enableBankingService = {
|
|
|
1316
1320
|
let iteration = 0;
|
|
1317
1321
|
do {
|
|
1318
1322
|
const result = await enableBankingService.getTransactions(accountUid, dateFrom, dateTo, continuationKey, psuHeaders);
|
|
1319
|
-
allTransactions.push(...result.transactions);
|
|
1320
1323
|
if (result.continuation_key && result.continuation_key === continuationKey) break;
|
|
1324
|
+
allTransactions.push(...result.transactions);
|
|
1321
1325
|
continuationKey = result.continuation_key;
|
|
1322
1326
|
iteration++;
|
|
1323
1327
|
} while (continuationKey && iteration < maxIterations);
|
|
@@ -1342,12 +1346,18 @@ function extractPsuHeaders(req) {
|
|
|
1342
1346
|
debug("Using PSU headers for public PSU IP");
|
|
1343
1347
|
return headers;
|
|
1344
1348
|
}
|
|
1349
|
+
var ASPSPS_WITHOUT_DATA_ACCESS_PSU_HEADERS = /* @__PURE__ */ new Set(["HypoVereinsbank"]);
|
|
1350
|
+
function dataAccessPsuHeaders(aspspName, psuHeaders) {
|
|
1351
|
+
if (aspspName && ASPSPS_WITHOUT_DATA_ACCESS_PSU_HEADERS.has(aspspName)) return;
|
|
1352
|
+
return psuHeaders;
|
|
1353
|
+
}
|
|
1345
1354
|
async function buildSessionResult(session, psuHeaders) {
|
|
1355
|
+
const accessHeaders = dataAccessPsuHeaders(session.aspsp?.name, psuHeaders);
|
|
1346
1356
|
const accountsWithBalances = await Promise.all(session.accounts.map(async (account) => {
|
|
1347
1357
|
const normalized = normalizeAccount(account, session.aspsp);
|
|
1348
1358
|
let balances = [];
|
|
1349
1359
|
try {
|
|
1350
|
-
balances = (await enableBankingService.getBalances(account.uid,
|
|
1360
|
+
balances = (await enableBankingService.getBalances(account.uid, accessHeaders)).balances.map(normalizeBalance);
|
|
1351
1361
|
} catch (err) {
|
|
1352
1362
|
debug("Failed to fetch balances for account %s: %s", account.uid, err);
|
|
1353
1363
|
}
|
|
@@ -1481,7 +1491,7 @@ app$7.post("/start-auth", handleError(async (req, res) => {
|
|
|
1481
1491
|
}
|
|
1482
1492
|
const state = v4();
|
|
1483
1493
|
try {
|
|
1484
|
-
const authResponse = await enableBankingService.startAuth(aspsp, redirectUrl, state, typeof maxConsentValidity === "number" ? maxConsentValidity : void 0, psuType === "business" ? "business" : "personal");
|
|
1494
|
+
const authResponse = await enableBankingService.startAuth(aspsp, redirectUrl, state, typeof maxConsentValidity === "number" ? maxConsentValidity : void 0, psuType === "business" ? "business" : "personal", extractPsuHeaders(req));
|
|
1485
1495
|
res.send({
|
|
1486
1496
|
status: "ok",
|
|
1487
1497
|
data: {
|
|
@@ -1616,7 +1626,7 @@ app$7.post("/poll-auth", handleError(async (req, res) => {
|
|
|
1616
1626
|
}
|
|
1617
1627
|
}));
|
|
1618
1628
|
app$7.post("/transactions", handleError(async (req, res) => {
|
|
1619
|
-
const { accountId, startDate } = req.body || {};
|
|
1629
|
+
const { accountId, startDate, aspspName } = req.body || {};
|
|
1620
1630
|
if (!accountId || !startDate) {
|
|
1621
1631
|
res.send({
|
|
1622
1632
|
status: "ok",
|
|
@@ -1627,10 +1637,10 @@ app$7.post("/transactions", handleError(async (req, res) => {
|
|
|
1627
1637
|
});
|
|
1628
1638
|
return;
|
|
1629
1639
|
}
|
|
1630
|
-
const psuHeaders = extractPsuHeaders(req);
|
|
1631
1640
|
try {
|
|
1632
1641
|
const dateTo = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1633
1642
|
const dateFrom = typeof startDate === "string" ? startDate : new Date(startDate).toISOString().split("T")[0];
|
|
1643
|
+
const psuHeaders = dataAccessPsuHeaders(aspspName, extractPsuHeaders(req));
|
|
1634
1644
|
const balances = (await enableBankingService.getBalances(accountId, psuHeaders)).balances.map(normalizeBalance);
|
|
1635
1645
|
let startingBalance = 0;
|
|
1636
1646
|
if (balances.length > 0) startingBalance = (balances.find((b) => b.balanceType === "CLAV") ?? balances[0]).balanceAmount.amount;
|
|
@@ -4996,14 +5006,16 @@ var SyncRequestSchema = messageDesc(file_sync, 3);
|
|
|
4996
5006
|
*/
|
|
4997
5007
|
var SyncResponseSchema = messageDesc(file_sync, 4);
|
|
4998
5008
|
//#endregion
|
|
4999
|
-
//#region src/app-sync/errors.
|
|
5009
|
+
//#region src/app-sync/errors.ts
|
|
5000
5010
|
var FileNotFound = class extends Error {
|
|
5011
|
+
details;
|
|
5001
5012
|
constructor(params = {}) {
|
|
5002
5013
|
super("File does not exist or you don't have access to it");
|
|
5003
5014
|
this.details = params;
|
|
5004
5015
|
}
|
|
5005
5016
|
};
|
|
5006
5017
|
var GenericFileError = class extends Error {
|
|
5018
|
+
details;
|
|
5007
5019
|
constructor(message, params = {}) {
|
|
5008
5020
|
super(message);
|
|
5009
5021
|
this.details = params;
|
|
@@ -5178,10 +5190,10 @@ var FilesService = class {
|
|
|
5178
5190
|
}
|
|
5179
5191
|
};
|
|
5180
5192
|
//#endregion
|
|
5181
|
-
//#region src/app-sync/validation.
|
|
5193
|
+
//#region src/app-sync/validation.ts
|
|
5182
5194
|
var SYNC_FORMAT_VERSION = 2;
|
|
5183
5195
|
var validateSyncedFile = (groupId, keyId, currentFile) => {
|
|
5184
|
-
if (currentFile.syncVersion == null || currentFile.syncVersion < SYNC_FORMAT_VERSION) return "file-old-version";
|
|
5196
|
+
if (currentFile.syncVersion == null || Number(currentFile.syncVersion) < SYNC_FORMAT_VERSION) return "file-old-version";
|
|
5185
5197
|
if (currentFile.groupId == null) return "file-needs-upload";
|
|
5186
5198
|
if ((currentFile.encryptMeta ? JSON.parse(currentFile.encryptMeta).keyId : null) !== currentFile.encryptKeyId) return "file-key-mismatch";
|
|
5187
5199
|
if (groupId !== currentFile.groupId) return "file-has-reset";
|
|
@@ -5426,14 +5438,18 @@ app$1.post("/upload-user-file", async (req, res) => {
|
|
|
5426
5438
|
res.status(400).send("invalid fileId");
|
|
5427
5439
|
return;
|
|
5428
5440
|
}
|
|
5429
|
-
|
|
5441
|
+
const groupIdHeader = req.headers["x-actual-group-id"];
|
|
5430
5442
|
const encryptMeta = extractSingleHeader(req, res, "x-actual-encrypt-meta");
|
|
5431
5443
|
if (res.headersSent) return;
|
|
5432
5444
|
const syncFormatVersion = extractSingleHeader(req, res, "x-actual-format");
|
|
5433
5445
|
if (res.headersSent) return;
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5446
|
+
let groupId = null;
|
|
5447
|
+
if (groupIdHeader !== void 0) {
|
|
5448
|
+
if (typeof groupIdHeader !== "string" || !isValidGroupId(groupIdHeader)) {
|
|
5449
|
+
res.status(400).send("invalid groupId");
|
|
5450
|
+
return;
|
|
5451
|
+
}
|
|
5452
|
+
groupId = groupIdHeader;
|
|
5437
5453
|
}
|
|
5438
5454
|
const keyId = encryptMeta && typeof encryptMeta === "string" ? JSON.parse(encryptMeta).keyId : null;
|
|
5439
5455
|
const filesService = new FilesService(getAccountDb());
|
|
@@ -5750,4 +5766,4 @@ async function run() {
|
|
|
5750
5766
|
//#endregion
|
|
5751
5767
|
export { run };
|
|
5752
5768
|
|
|
5753
|
-
//# sourceMappingURL=app-
|
|
5769
|
+
//# sourceMappingURL=app-C9cIy7Ri.js.map
|