@actual-app/sync-server 26.8.0 → 26.9.0-nightly.20260803
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-DOEWw8Fg.js";
|
|
2
2
|
//#region app.ts
|
|
3
3
|
run().then(() => {
|
|
4
|
-
import("./chunks/app-
|
|
4
|
+
import("./chunks/app-Du06KVql.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,0BAAqB;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"}
|
|
@@ -4437,19 +4437,31 @@ app$2.post("/status", handleError(async (req, res) => {
|
|
|
4437
4437
|
}));
|
|
4438
4438
|
app$2.post("/accounts", handleError(async (req, res) => {
|
|
4439
4439
|
let accessKey = secretsService.get(SecretName.simplefin_accessKey);
|
|
4440
|
-
|
|
4440
|
+
if (isInvalidAccessKey(accessKey)) {
|
|
4441
|
+
const token = secretsService.get(SecretName.simplefin_token);
|
|
4442
|
+
if (token == null || isForbidden(token)) {
|
|
4443
|
+
invalidToken(res);
|
|
4444
|
+
return;
|
|
4445
|
+
}
|
|
4446
|
+
const claimUrl = decodeClaimUrl(token);
|
|
4447
|
+
if (claimUrl == null) {
|
|
4448
|
+
console.log("SimpleFIN setup token does not decode to a claim URL - re-enter the token");
|
|
4449
|
+
invalidToken(res);
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4452
|
+
try {
|
|
4453
|
+
accessKey = await claimAccessKey(claimUrl);
|
|
4454
|
+
} catch (e) {
|
|
4455
|
+
console.log("Failed to claim the SimpleFIN setup token:");
|
|
4456
|
+
serverDown(e, res);
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4441
4459
|
if (isInvalidAccessKey(accessKey)) {
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
accessKey = await getAccessKey(token);
|
|
4446
|
-
if (isInvalidAccessKey(accessKey)) throw new Error("No access key");
|
|
4447
|
-
secretsService.set(SecretName.simplefin_accessKey, accessKey);
|
|
4448
|
-
}
|
|
4460
|
+
console.log(`SimpleFIN rejected the setup token claim: ${accessKey.slice(0, 200) || "(empty response)"}`);
|
|
4461
|
+
invalidToken(res);
|
|
4462
|
+
return;
|
|
4449
4463
|
}
|
|
4450
|
-
|
|
4451
|
-
invalidToken(res);
|
|
4452
|
-
return;
|
|
4464
|
+
secretsService.set(SecretName.simplefin_accessKey, accessKey);
|
|
4453
4465
|
}
|
|
4454
4466
|
try {
|
|
4455
4467
|
const accounts = await getAccounts(accessKey, null, null, null, true);
|
|
@@ -4620,6 +4632,7 @@ function serverDown(e, res) {
|
|
|
4620
4632
|
}
|
|
4621
4633
|
});
|
|
4622
4634
|
}
|
|
4635
|
+
var ACCESS_KEY_FORMAT = /^.*\/\/.*:.*@.*$/;
|
|
4623
4636
|
function parseAccessKey(accessKey) {
|
|
4624
4637
|
let scheme = null;
|
|
4625
4638
|
let rest = null;
|
|
@@ -4627,7 +4640,7 @@ function parseAccessKey(accessKey) {
|
|
|
4627
4640
|
let username = null;
|
|
4628
4641
|
let password = null;
|
|
4629
4642
|
let baseUrl = null;
|
|
4630
|
-
if (!accessKey || !
|
|
4643
|
+
if (!accessKey || !ACCESS_KEY_FORMAT.test(accessKey)) {
|
|
4631
4644
|
console.log("Invalid SimpleFIN access key");
|
|
4632
4645
|
throw new Error(`Invalid access key`);
|
|
4633
4646
|
}
|
|
@@ -4641,19 +4654,31 @@ function parseAccessKey(accessKey) {
|
|
|
4641
4654
|
password
|
|
4642
4655
|
};
|
|
4643
4656
|
}
|
|
4644
|
-
|
|
4645
|
-
const
|
|
4646
|
-
|
|
4647
|
-
|
|
4657
|
+
function decodeClaimUrl(base64Token) {
|
|
4658
|
+
const decoded = Buffer.from(base64Token, "base64").toString();
|
|
4659
|
+
let url;
|
|
4660
|
+
try {
|
|
4661
|
+
url = new URL(decoded);
|
|
4662
|
+
} catch {
|
|
4663
|
+
return null;
|
|
4664
|
+
}
|
|
4665
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
4666
|
+
return decoded;
|
|
4667
|
+
}
|
|
4668
|
+
async function claimAccessKey(claimUrl) {
|
|
4669
|
+
await assertUrlAllowed(claimUrl, { allowPrivateNetwork: true });
|
|
4670
|
+
const response = await fetch(claimUrl, {
|
|
4648
4671
|
method: "POST",
|
|
4649
4672
|
redirect: "manual"
|
|
4650
|
-
})
|
|
4673
|
+
});
|
|
4674
|
+
if (!response.ok && response.status !== 403) throw new Error(`SimpleFIN claim failed with HTTP ${response.status}`);
|
|
4675
|
+
return (await response.text()).trim();
|
|
4651
4676
|
}
|
|
4652
4677
|
function isForbidden(value) {
|
|
4653
4678
|
return typeof value === "string" && value.startsWith("Forbidden");
|
|
4654
4679
|
}
|
|
4655
4680
|
function isInvalidAccessKey(accessKey) {
|
|
4656
|
-
return typeof accessKey !== "string" || accessKey
|
|
4681
|
+
return typeof accessKey !== "string" || isForbidden(accessKey) || !ACCESS_KEY_FORMAT.test(accessKey);
|
|
4657
4682
|
}
|
|
4658
4683
|
async function getTransactions(accessKey, accounts, startDate, endDate) {
|
|
4659
4684
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -5685,4 +5710,4 @@ async function run() {
|
|
|
5685
5710
|
//#endregion
|
|
5686
5711
|
export { run };
|
|
5687
5712
|
|
|
5688
|
-
//# sourceMappingURL=app-
|
|
5713
|
+
//# sourceMappingURL=app-Du06KVql.js.map
|