Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge actual server into actual #3

Merged
merged 397 commits into from
Feb 5, 2025

Conversation

MikesGlitch
Copy link
Owner

No description provided.

Jackenmen and others added 30 commits March 24, 2023 21:59
…ints (#166)

Exposes IBAN in the API which is required for
actualbudget#766 to work reliably.
When working on something else, I noticed that `.test.js` files were not
running due to `jest.config.json` not including them. I went ahead and
re-enabled these tests to make sure that unit tests are actually being
run.
…lbudget#141)

Users in actualbudget#99 report that Actual in Docker runs on armv7 platforms,
although a bit sluggish. I confirmed that the base images for Debian and
Alpine support the linux/arm/v7 target and have added them to the
platform list in the GitHub Actions workflow.

At least one user confirms it works with the bullseye default
`Dockerfile`, but before merging it would be great if someone can
confirm it works with the Dockerfile.alpine image:

```
git clone https://github.com/jamesmortensen/actual-server.git
cd actual-server
git checkout armv7-image
docker build -t actual-server -f Dockerfile.alpine .
docker run -p 5006:5006 actual-server
```

---------

Co-authored-by: Jed Fox <[email protected]>
Update all Docker Hub references to new `actualbudget` organization from
`jlongster` personal account.
We're officially an org now!

A bit of markdown/yaml auto-formatting snuck in, too.

Closes actualbudget#364
Corresponding update for the docs site in
actualbudget/docs#144

Simultaneous to merging, we need to update our `DOCKER_HUB_*` GitHub
secrets in this repo.

---------

Co-authored-by: Jed Fox <[email protected]>
This will help people sort out configuration issues. Will open a PR to
the docs as well to guide people to troubleshoot using this!
GitHub CI log:
```
[linux/arm/v6 base 6/8] RUN yarn workspaces focus --all --production
204.6 ➤ YN0007: │ bcrypt@npm:5.1.0 must be built because it never has been before or the last one failed
204.6 ➤ YN0007: │ better-sqlite3@npm:8.2.0 must be built because it never has been before or the last one failed
...
[linux/arm/v7 base 6/8] RUN yarn workspaces focus --all --production
203.8 ➤ YN0007: │ bcrypt@npm:5.1.0 must be built because it never has been before or the last one failed
203.8 ➤ YN0007: │ better-sqlite3@npm:8.2.0 must be built because it never has been before or the last one failed
```

It seems that both armv6 and armv7 have the same issues with `bcrypt`
and `better-sqlite3` not being built. These packages are required to
build from source, luckily QEMU use armv7l for compiling.

Tested and working on RPi Zero W.

---------

Co-authored-by: Jed Fox <[email protected]>
Seems like we already added `tsc` to build the project, but we use the
wrong babel preset (Flow) instead of the specific TS one. This is only
used in testing to make Jest work (from what I can tell).
CodeQL keeps yelling at us about this… I’m not sure if the filter is
smart enough to use this rate limit middleware to remove the warnings,
but at least we will be setting a reasonable bound on attempts to crack
the server password.
Co-authored-by: Matiss Janis Aboltins <[email protected]>
…192)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…(#190)

Helps with actualbudget#919 by adding the `all` field wit both
pending and booked transactions to the output of
`getTransactionsWithBalance()` and, by extension, the
`/nordigen/transactions` endpoint.

I could alter the `getTransactions()` to return the `all` field as well
but I figured that keeping it such that it returns the output from
Nordigen API 1:1 might be better so I left it as is. If you don't agree,
let me know and I'll update this.
A small fix: returning JSON response instead of plain-text.

The frontend always expects a JSON response. So a tiny fix here..
Previously, the latest artifact list was requested unauthenticated using
`ADD "https://api.github.com/..." /tmp/artifacts.json`. While this works
locally, on GitHub’s servers it seems that the per-IP rate limit was
exceeded. There isn’t a way to get Docker to pass the `Authorization`
header that I know of, so this work has been moved to an external shell
script that pulls down the relevant data.
Web: actualbudget#1087
Server: actualbudget/actual-server#207
Docs: actualbudget/docs#179

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Remove the bulk in favour of links to our core docs.

---------

Co-authored-by: Jed Fox <[email protected]>
Sthaagg and others added 26 commits December 19, 2024 21:25
…et#527)

* Added command lines to enable/disable openid

* md

* Update src/scripts/disable-openid.js

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* changed error codes based on code rabbit review

* fix for github auth

* code review

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* 🔖 (25.1.0)

* Remove used release notes

* Update web package

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* chore: payee name for google pay

* chore: revert notes to include all data

* chore: release note

* chore: fix tests
…budget#535)

* Added special corner case

* One additional validation + Release notes

* Fixed duplicate code

* Improved comment

* Improved comment

---------

Co-authored-by: spideraxal <[email protected]>
* ✨ add bank adapter for BOI

* Remove console log
@MikesGlitch MikesGlitch marked this pull request as ready for review February 5, 2025 22:21
}

if (Array.isArray(accountId) != Array.isArray(startDate)) {
console.log(accountId, startDate);

Check failure

Code scanning / CodeQL

Use of externally-controlled format string High

Format string depends on a
user-provided value
.

Copilot Autofix AI 15 days ago

To fix the problem, we need to ensure that the user-provided values are safely included in the format string. This can be achieved by using the %s specifier in the console.log statement and passing the accountId and startDate as additional arguments. This approach will prevent any format string vulnerabilities.

  • Modify the console.log statement on line 79 to use the %s specifier.
  • Pass the accountId and startDate as additional arguments to the console.log function.
Suggested changeset 1
packages/sync-server/src/app-simplefin/app-simplefin.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/sync-server/src/app-simplefin/app-simplefin.js b/packages/sync-server/src/app-simplefin/app-simplefin.js
--- a/packages/sync-server/src/app-simplefin/app-simplefin.js
+++ b/packages/sync-server/src/app-simplefin/app-simplefin.js
@@ -78,3 +78,3 @@
     if (Array.isArray(accountId) != Array.isArray(startDate)) {
-      console.log(accountId, startDate);
+      console.log('accountId: %s, startDate: %s', accountId, startDate);
       throw new Error(
@@ -84,3 +84,3 @@
     if (Array.isArray(accountId) && accountId.length !== startDate.length) {
-      console.log(accountId, startDate);
+      console.log('accountId: %s, startDate: %s', accountId, startDate);
       throw new Error('accountId and startDate arrays must be the same length');
EOF
@@ -78,3 +78,3 @@
if (Array.isArray(accountId) != Array.isArray(startDate)) {
console.log(accountId, startDate);
console.log('accountId: %s, startDate: %s', accountId, startDate);
throw new Error(
@@ -84,3 +84,3 @@
if (Array.isArray(accountId) && accountId.length !== startDate.length) {
console.log(accountId, startDate);
console.log('accountId: %s, startDate: %s', accountId, startDate);
throw new Error('accountId and startDate arrays must be the same length');
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
);
}
if (Array.isArray(accountId) && accountId.length !== startDate.length) {
console.log(accountId, startDate);

Check failure

Code scanning / CodeQL

Use of externally-controlled format string High

Format string depends on a
user-provided value
.

Copilot Autofix AI 15 days ago

To fix the problem, we need to ensure that the user-provided input is not directly used in the format string. Instead, we should use a %s specifier in the format string and pass the untrusted data as corresponding arguments. This will prevent any unexpected format specifiers from causing issues.

Specifically, we will modify the console.log statements on lines 79 and 85 to use %s specifiers and pass the accountId and startDate variables as arguments.

Suggested changeset 1
packages/sync-server/src/app-simplefin/app-simplefin.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/sync-server/src/app-simplefin/app-simplefin.js b/packages/sync-server/src/app-simplefin/app-simplefin.js
--- a/packages/sync-server/src/app-simplefin/app-simplefin.js
+++ b/packages/sync-server/src/app-simplefin/app-simplefin.js
@@ -78,3 +78,3 @@
     if (Array.isArray(accountId) != Array.isArray(startDate)) {
-      console.log(accountId, startDate);
+      console.log('accountId: %s, startDate: %s', accountId, startDate);
       throw new Error(
@@ -84,3 +84,3 @@
     if (Array.isArray(accountId) && accountId.length !== startDate.length) {
-      console.log(accountId, startDate);
+      console.log('accountId: %s, startDate: %s', accountId, startDate);
       throw new Error('accountId and startDate arrays must be the same length');
EOF
@@ -78,3 +78,3 @@
if (Array.isArray(accountId) != Array.isArray(startDate)) {
console.log(accountId, startDate);
console.log('accountId: %s, startDate: %s', accountId, startDate);
throw new Error(
@@ -84,3 +84,3 @@
if (Array.isArray(accountId) && accountId.length !== startDate.length) {
console.log(accountId, startDate);
console.log('accountId: %s, startDate: %s', accountId, startDate);
throw new Error('accountId and startDate arrays must be the same length');
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
let username = null;
let password = null;
let baseUrl = null;
if (!accessKey || !accessKey.match(/^.*\/\/.*:.*@.*$/)) {

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on
a user-provided value
may run slow on strings starting with '//' and with many repetitions of '//'.
This
regular expression
that depends on
a user-provided value
may run slow on strings starting with '//:' and with many repetitions of ':'.
This
regular expression
that depends on
a user-provided value
may run slow on strings starting with '//:@' and with many repetitions of '@'.
Comment on lines +293 to +297
const req = https.request(new URL(token), options, (res) => {
res.on('data', (d) => {
resolve(d.toString());
});
});

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix AI 15 days ago

To fix the SSRF vulnerability, we need to ensure that the URL used in the https.request call is not directly influenced by user input. Instead, we should validate the token against a whitelist of allowed URLs or domains. This can be achieved by extracting the hostname from the URL and checking it against a predefined list of allowed hostnames.

  1. Extract the hostname from the token URL.
  2. Validate the hostname against a whitelist of allowed hostnames.
  3. If the hostname is not in the whitelist, reject the request.
Suggested changeset 1
packages/sync-server/src/app-simplefin/app-simplefin.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/sync-server/src/app-simplefin/app-simplefin.js b/packages/sync-server/src/app-simplefin/app-simplefin.js
--- a/packages/sync-server/src/app-simplefin/app-simplefin.js
+++ b/packages/sync-server/src/app-simplefin/app-simplefin.js
@@ -286,2 +286,9 @@
   const token = Buffer.from(base64Token, 'base64').toString();
+  const url = new URL(token);
+  const allowedHostnames = ['allowed-hostname1.com', 'allowed-hostname2.com'];
+  
+  if (!allowedHostnames.includes(url.hostname)) {
+    throw new Error('Invalid hostname in token URL');
+  }
+  
   const options = {
@@ -292,3 +299,3 @@
   return new Promise((resolve, reject) => {
-    const req = https.request(new URL(token), options, (res) => {
+    const req = https.request(url, options, (res) => {
       res.on('data', (d) => {
EOF
@@ -286,2 +286,9 @@
const token = Buffer.from(base64Token, 'base64').toString();
const url = new URL(token);
const allowedHostnames = ['allowed-hostname1.com', 'allowed-hostname2.com'];

if (!allowedHostnames.includes(url.hostname)) {
throw new Error('Invalid hostname in token URL');
}

const options = {
@@ -292,3 +299,3 @@
return new Promise((resolve, reject) => {
const req = https.request(new URL(token), options, (res) => {
const req = https.request(url, options, (res) => {
res.on('data', (d) => {
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
Comment on lines +361 to +386
const req = https.request(
new URL(`${sfin.baseUrl}/accounts${queryString}`),
options,
(res) => {
let data = '';
res.on('data', (d) => {
data += d;
});
res.on('end', () => {
if (res.statusCode === 403) {
reject(new Error('Forbidden'));
} else {
try {
const results = JSON.parse(data);
results.sferrors = results.errors;
results.hasError = false;
results.errors = {};
resolve(results);
} catch (e) {
console.log(`Error parsing JSON response: ${data}`);
reject(e);
}
}
});
},
);

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix AI 15 days ago

To fix the problem, we need to ensure that the baseUrl used in the https.request call is validated against a whitelist of allowed URLs. This will prevent user-controlled input from being used directly in the URL, mitigating the risk of SSRF attacks.

  1. Create a whitelist of allowed base URLs.
  2. Validate the baseUrl against this whitelist before using it in the https.request call.
  3. If the baseUrl is not in the whitelist, reject the request or handle it appropriately.
Suggested changeset 1
packages/sync-server/src/app-simplefin/app-simplefin.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/sync-server/src/app-simplefin/app-simplefin.js b/packages/sync-server/src/app-simplefin/app-simplefin.js
--- a/packages/sync-server/src/app-simplefin/app-simplefin.js
+++ b/packages/sync-server/src/app-simplefin/app-simplefin.js
@@ -262,2 +262,4 @@
 
+const allowedBaseUrls = ['https://api.simplefin.com', 'https://sandbox.simplefin.com'];
+
 function parseAccessKey(accessKey) {
@@ -277,2 +279,8 @@
   baseUrl = `${scheme}//${rest}`;
+  
+  if (!allowedBaseUrls.includes(baseUrl)) {
+    console.log(`Disallowed base URL: ${baseUrl}`);
+    throw new Error(`Disallowed base URL`);
+  }
+  
   return {
EOF
@@ -262,2 +262,4 @@

const allowedBaseUrls = ['https://api.simplefin.com', 'https://sandbox.simplefin.com'];

function parseAccessKey(accessKey) {
@@ -277,2 +279,8 @@
baseUrl = `${scheme}//${rest}`;

if (!allowedBaseUrls.includes(baseUrl)) {
console.log(`Disallowed base URL: ${baseUrl}`);
throw new Error(`Disallowed base URL`);
}

return {
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
}

try {
await fs.writeFile(getPathForUserFile(fileId), req.body);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix AI 15 days ago

To fix the problem, we need to ensure that the file path constructed from the user-provided fileId is safe and does not allow directory traversal or access to unintended files. We can achieve this by normalizing the path and ensuring it is contained within a predefined root directory.

  1. Define a root directory where user files are stored.
  2. Use path.resolve to normalize the constructed file path.
  3. Verify that the normalized path starts with the root directory path.
Suggested changeset 1
packages/sync-server/src/app-sync.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/sync-server/src/app-sync.js b/packages/sync-server/src/app-sync.js
--- a/packages/sync-server/src/app-sync.js
+++ b/packages/sync-server/src/app-sync.js
@@ -10,2 +10,3 @@
 import { getPathForUserFile, getPathForGroupFile } from './util/paths.js';
+import path from 'path';
 
@@ -232,3 +233,9 @@
   try {
-    await fs.writeFile(getPathForUserFile(fileId), req.body);
+    const ROOT_DIR = '/path/to/root/directory';
+    let userFilePath = path.resolve(ROOT_DIR, getPathForUserFile(fileId));
+    if (!userFilePath.startsWith(ROOT_DIR)) {
+      res.status(400).send('Invalid file path');
+      return;
+    }
+    await fs.writeFile(userFilePath, req.body);
   } catch (err) {
EOF
@@ -10,2 +10,3 @@
import { getPathForUserFile, getPathForGroupFile } from './util/paths.js';
import path from 'path';

@@ -232,3 +233,9 @@
try {
await fs.writeFile(getPathForUserFile(fileId), req.body);
const ROOT_DIR = '/path/to/root/directory';
let userFilePath = path.resolve(ROOT_DIR, getPathForUserFile(fileId));
if (!userFilePath.startsWith(ROOT_DIR)) {
res.status(400).send('Invalid file path');
return;
}
await fs.writeFile(userFilePath, req.body);
} catch (err) {
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
}

res.setHeader('Content-Disposition', `attachment;filename=${fileId}`);
res.sendFile(getPathForUserFile(fileId));

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix AI 15 days ago

To fix the problem, we need to ensure that the constructed file path is contained within a safe root folder. We can achieve this by normalizing the path using path.resolve and then checking that the normalized path starts with the root folder. This will prevent path traversal attacks by removing any ".." segments and ensuring the file path is within the intended directory.

  1. Import the path module.
  2. Define a constant for the root directory where user files are stored.
  3. Normalize the file path using path.resolve.
  4. Check that the normalized path starts with the root directory.
  5. If the check fails, return a 403 status code.
Suggested changeset 1
packages/sync-server/src/app-sync.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/sync-server/src/app-sync.js b/packages/sync-server/src/app-sync.js
--- a/packages/sync-server/src/app-sync.js
+++ b/packages/sync-server/src/app-sync.js
@@ -10,2 +10,3 @@
 import { getPathForUserFile, getPathForGroupFile } from './util/paths.js';
+import path from 'path';
 
@@ -13,2 +14,4 @@
 
+const USER_FILES_ROOT = '/path/to/user/files';
+
 import { SyncProtoBuf } from '@actual-app/crdt';
@@ -295,4 +298,9 @@
 
+  const filePath = path.resolve(USER_FILES_ROOT, getPathForUserFile(fileId));
+  if (!filePath.startsWith(USER_FILES_ROOT)) {
+    res.status(403).send('Access denied');
+    return;
+  }
   res.setHeader('Content-Disposition', `attachment;filename=${fileId}`);
-  res.sendFile(getPathForUserFile(fileId));
+  res.sendFile(filePath);
 });
EOF
@@ -10,2 +10,3 @@
import { getPathForUserFile, getPathForGroupFile } from './util/paths.js';
import path from 'path';

@@ -13,2 +14,4 @@

const USER_FILES_ROOT = '/path/to/user/files';

import { SyncProtoBuf } from '@actual-app/crdt';
@@ -295,4 +298,9 @@

const filePath = path.resolve(USER_FILES_ROOT, getPathForUserFile(fileId));
if (!filePath.startsWith(USER_FILES_ROOT)) {
res.status(403).send('Access denied');
return;
}
res.setHeader('Content-Disposition', `attachment;filename=${fileId}`);
res.sendFile(getPathForUserFile(fileId));
res.sendFile(filePath);
});
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
// Source: https://expressjs.com/en/guide/error-handling.html
return next(err);
}
console.log(`Error on endpoint ${req.url}`, err.message, err.stack);

Check failure

Code scanning / CodeQL

Use of externally-controlled format string High

Format string depends on a
user-provided value
.

Copilot Autofix AI 15 days ago

To fix the problem, we should avoid directly embedding the untrusted req.url value in the format string. Instead, we can use a %s specifier in the format string and pass the req.url as a separate argument to ensure it is treated as a string. This approach will prevent any potential format string vulnerabilities.

Suggested changeset 1
packages/sync-server/src/util/middlewares.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/sync-server/src/util/middlewares.js b/packages/sync-server/src/util/middlewares.js
--- a/packages/sync-server/src/util/middlewares.js
+++ b/packages/sync-server/src/util/middlewares.js
@@ -24,3 +24,3 @@
   }
-  console.log(`Error on endpoint ${req.url}`, err.message, err.stack);
+  console.log('Error on endpoint %s', req.url, err.message, err.stack);
   res.status(500).send({ status: 'error', reason: 'internal-error' });
EOF
@@ -24,3 +24,3 @@
}
console.log(`Error on endpoint ${req.url}`, err.message, err.stack);
console.log('Error on endpoint %s', req.url, err.message, err.stack);
res.status(500).send({ status: 'error', reason: 'internal-error' });
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
@MikesGlitch MikesGlitch merged commit e8f9f43 into test-merge Feb 5, 2025
5 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.