Compare commits
3 Commits
master
...
matiss/715
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2854e049d2 | ||
|
|
d99b19b911 | ||
|
|
67d1abda52 |
@@ -1,74 +0,0 @@
|
||||
---
|
||||
description: Rules for AI-generated commits and pull requests
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# PR and Commit Rules for AI Agents
|
||||
|
||||
Canonical source: `.github/agents/pr-and-commit-rules.md`
|
||||
|
||||
## Commit Rules
|
||||
|
||||
### [AI] Prefix Requirement
|
||||
|
||||
**ALL commit messages MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `[AI] Fix type error in account validation`
|
||||
- `[AI] Add support for new transaction categories`
|
||||
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
|
||||
- `Add support for new transaction categories` (MISSING PREFIX - NOT ALLOWED)
|
||||
|
||||
### Git Safety Rules
|
||||
|
||||
- **Never** update git config
|
||||
- **Never** run destructive git operations (force push, hard reset) unless the user explicitly requests it
|
||||
- **Never** skip hooks (`--no-verify`, `--no-gpg-sign`)
|
||||
- **Never** force push to `main`/`master`
|
||||
- **Never** commit unless explicitly asked by the user
|
||||
|
||||
## Pre-Commit Quality Checklist
|
||||
|
||||
Before committing, ensure all of the following:
|
||||
|
||||
- [ ] Commit message is prefixed with `[AI]`
|
||||
- [ ] `yarn typecheck` passes
|
||||
- [ ] `yarn lint:fix` has been run
|
||||
- [ ] Relevant tests pass
|
||||
- [ ] User-facing strings are translated
|
||||
- [ ] Code style conventions followed (see `AGENTS.md` for full style guide)
|
||||
|
||||
## Pull Request Rules
|
||||
|
||||
### [AI] Prefix Requirement
|
||||
|
||||
**ALL pull request titles MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `[AI] Fix type error in account validation`
|
||||
- `[AI] Add support for new transaction categories`
|
||||
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
|
||||
|
||||
### Labels
|
||||
|
||||
Add the **"AI generated"** label to all AI-created pull requests.
|
||||
|
||||
### PR Template: Do Not Fill In
|
||||
|
||||
- **NEVER fill in the PR template** (`.github/PULL_REQUEST_TEMPLATE.md`). Leave all blank spaces and placeholder comments as-is.
|
||||
- **Exception**: If a human **explicitly asks** you to fill out the PR template, then fill it out **in Chinese** (简体中文).
|
||||
|
||||
## Quick-Reference Workflow
|
||||
|
||||
1. Make your changes
|
||||
2. Run `yarn typecheck` — fix any errors
|
||||
3. Run `yarn lint:fix` — fix any remaining lint errors
|
||||
4. Run relevant tests (`yarn test` for all, or workspace-specific)
|
||||
5. Stage files and commit with `[AI]` prefix — do not skip hooks
|
||||
6. When creating a PR:
|
||||
- Use `[AI]` prefix in the title
|
||||
- Add the `"AI generated"` label
|
||||
- Leave the PR template blank (do not fill it in)
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// overview:
|
||||
// 1. Identify the migrations in packages/loot-core/migrations/* on `master` and HEAD
|
||||
// 2. Make sure that any new migrations on HEAD are dated after the latest migration on `master`.
|
||||
|
||||
import { spawnSync } from 'child_process';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
const { spawnSync } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const migrationsDir = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'..',
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'packages',
|
||||
@@ -16,7 +16,7 @@ const migrationsDir = path.join(
|
||||
'migrations',
|
||||
);
|
||||
|
||||
function readMigrations(ref: string) {
|
||||
function readMigrations(ref) {
|
||||
const { stdout } = spawnSync('git', [
|
||||
'ls-tree',
|
||||
'--name-only',
|
||||
70
.github/agents/pr-and-commit-rules.md
vendored
@@ -1,70 +0,0 @@
|
||||
# PR and Commit Rules for AI Agents
|
||||
|
||||
This is the single source of truth for all commit and pull request rules that AI agents must follow when working with Actual Budget.
|
||||
|
||||
## Commit Rules
|
||||
|
||||
### [AI] Prefix Requirement
|
||||
|
||||
**ALL commit messages MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `[AI] Fix type error in account validation`
|
||||
- `[AI] Add support for new transaction categories`
|
||||
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
|
||||
- `Add support for new transaction categories` (MISSING PREFIX - NOT ALLOWED)
|
||||
|
||||
### Git Safety Rules
|
||||
|
||||
- **Never** update git config
|
||||
- **Never** run destructive git operations (force push, hard reset) unless the user explicitly requests it
|
||||
- **Never** skip hooks (`--no-verify`, `--no-gpg-sign`)
|
||||
- **Never** force push to `main`/`master`
|
||||
- **Never** commit unless explicitly asked by the user
|
||||
|
||||
## Pre-Commit Quality Checklist
|
||||
|
||||
Before committing, ensure all of the following:
|
||||
|
||||
- [ ] Commit message is prefixed with `[AI]`
|
||||
- [ ] `yarn typecheck` passes
|
||||
- [ ] `yarn lint:fix` has been run
|
||||
- [ ] Relevant tests pass
|
||||
- [ ] User-facing strings are translated
|
||||
- [ ] Code style conventions followed (see `AGENTS.md` for full style guide)
|
||||
|
||||
## Pull Request Rules
|
||||
|
||||
### [AI] Prefix Requirement
|
||||
|
||||
**ALL pull request titles MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `[AI] Fix type error in account validation`
|
||||
- `[AI] Add support for new transaction categories`
|
||||
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
|
||||
|
||||
### Labels
|
||||
|
||||
Add the **"AI generated"** label to all AI-created pull requests. This helps maintainers understand the nature of the contribution.
|
||||
|
||||
### PR Template: Do Not Fill In
|
||||
|
||||
- **NEVER fill in the PR template** (`.github/PULL_REQUEST_TEMPLATE.md`). Leave all blank spaces and placeholder comments as-is. Humans are expected to fill in the Description, Related issue(s), Testing, and Checklist sections.
|
||||
- **Exception**: If a human **explicitly asks** you to fill out the PR template, then fill it out **in Chinese**, using Chinese characters (简体中文) for all content you add.
|
||||
|
||||
## Quick-Reference Workflow
|
||||
|
||||
Follow these steps when committing and creating PRs:
|
||||
|
||||
1. Make your changes
|
||||
2. Run `yarn typecheck` — fix any errors
|
||||
3. Run `yarn lint:fix` — fix any remaining lint errors
|
||||
4. Run relevant tests (`yarn test` for all, or workspace-specific)
|
||||
5. Stage files and commit with `[AI]` prefix — do not skip hooks
|
||||
6. When creating a PR:
|
||||
- Use `[AI]` prefix in the title
|
||||
- Add the `"AI generated"` label
|
||||
- Leave the PR template blank (do not fill it in)
|
||||
7
.github/workflows/check.yml
vendored
@@ -60,9 +60,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
download-translations: 'false'
|
||||
node-version: 22
|
||||
- name: Check migrations
|
||||
run: yarn workspace @actual-app/ci-actions tsx bin/check-migrations.ts
|
||||
run: node ./.github/actions/check-migrations.js
|
||||
|
||||
42
AGENTS.md
@@ -44,9 +44,25 @@ yarn start:desktop
|
||||
|
||||
### ⚠️ CRITICAL REQUIREMENT: AI-Generated Commit Messages and PR Titles
|
||||
|
||||
**ALL commit messages and PR titles MUST be prefixed with `[AI]`.** No exceptions.
|
||||
**THIS IS A MANDATORY REQUIREMENT THAT MUST BE FOLLOWED WITHOUT EXCEPTION:**
|
||||
|
||||
See [PR and Commit Rules](.github/agents/pr-and-commit-rules.md) for the full specification, including git safety rules, pre-commit checklist, and PR workflow.
|
||||
- **ALL commit messages MUST be prefixed with `[AI]`**
|
||||
- **ALL pull request titles MUST be prefixed with `[AI]`**
|
||||
|
||||
**Examples:**
|
||||
|
||||
- ✅ `[AI] Fix type error in account validation`
|
||||
- ✅ `[AI] Add support for new transaction categories`
|
||||
- ❌ `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
|
||||
- ❌ `Add support for new transaction categories` (MISSING PREFIX - NOT ALLOWED)
|
||||
|
||||
**This requirement applies to:**
|
||||
|
||||
- Every single commit message created by AI agents
|
||||
- Every single pull request title created by AI agents
|
||||
- No exceptions are permitted
|
||||
|
||||
**This is a hard requirement that agents MUST follow. Failure to include the `[AI]` prefix is a violation of these instructions.**
|
||||
|
||||
### Task Orchestration with Lage
|
||||
|
||||
@@ -345,7 +361,13 @@ Always maintain newlines between import groups.
|
||||
|
||||
**Git Commands:**
|
||||
|
||||
See [PR and Commit Rules](.github/agents/pr-and-commit-rules.md) for complete git safety rules, commit message requirements, and PR workflow.
|
||||
- **MANDATORY: ALL commit messages MUST be prefixed with `[AI]`** - This is a hard requirement with no exceptions
|
||||
- **MANDATORY: ALL pull request titles MUST be prefixed with `[AI]`** - This is a hard requirement with no exceptions
|
||||
- Never update git config
|
||||
- Never run destructive git operations (force push, hard reset) unless explicitly requested
|
||||
- Never skip hooks (--no-verify, --no-gpg-sign)
|
||||
- Never force push to main/master
|
||||
- Never commit unless explicitly asked
|
||||
|
||||
## File Structure Patterns
|
||||
|
||||
@@ -544,7 +566,7 @@ Icons in `packages/component-library/src/icons/` are auto-generated. Don't manua
|
||||
|
||||
Before committing changes, ensure:
|
||||
|
||||
- [ ] Commit and PR rules followed (see [PR and Commit Rules](.github/agents/pr-and-commit-rules.md))
|
||||
- [ ] **MANDATORY: Commit message is prefixed with `[AI]`** - This is a hard requirement with no exceptions
|
||||
- [ ] `yarn typecheck` passes
|
||||
- [ ] `yarn lint:fix` has been run
|
||||
- [ ] Relevant tests pass
|
||||
@@ -557,7 +579,17 @@ Before committing changes, ensure:
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
See [PR and Commit Rules](.github/agents/pr-and-commit-rules.md) for complete PR creation rules, including title prefix requirements, labeling, and PR template handling.
|
||||
When creating pull requests:
|
||||
|
||||
- **MANDATORY PREFIX REQUIREMENT**: **ALL pull request titles MUST be prefixed with `[AI]`** - This is a hard requirement that MUST be followed without exception
|
||||
- ✅ Correct: `[AI] Fix type error in account validation`
|
||||
- ❌ Incorrect: `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
|
||||
- **AI-Generated PRs**: If you create a PR using AI assistance, add the **"AI generated"** label to the pull request. This helps maintainers understand the nature of the contribution.
|
||||
|
||||
### PR Template: Do Not Fill In
|
||||
|
||||
- **NEVER fill in the PR template** (`.github/PULL_REQUEST_TEMPLATE.md`). Leave all blank spaces and placeholder comments as-is. We expect **humans** to fill in the Description, Related issue(s), Testing, and Checklist sections.
|
||||
- **Exception**: If a human **explicitly asks** you to fill out the PR template, then fill it out **in Chinese**, using Chinese characters (简体中文) for all content you add.
|
||||
|
||||
## Code Review Guidelines
|
||||
|
||||
|
||||
@@ -3,18 +3,9 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"tsx": "node --import=extensionless/register --experimental-strip-types",
|
||||
"test": "vitest --run",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"test": "vitest --run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"extensionless": "^2.0.6",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"extensionless": {
|
||||
"lookFor": [
|
||||
"ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": [],
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"types": ["node"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["src/**/*", "bin/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@@ -34,7 +34,10 @@ import type { AccountEntity, TransactionEntity } from 'loot-core/types/models';
|
||||
|
||||
import { lookupName, Status } from './TransactionEdit';
|
||||
|
||||
import { makeAmountFullStyle } from '@desktop-client/components/budget/util';
|
||||
import {
|
||||
makeAmountFullStyle,
|
||||
makeBalanceAmountStyle,
|
||||
} from '@desktop-client/components/budget/util';
|
||||
import { useAccount } from '@desktop-client/hooks/useAccount';
|
||||
import { useCachedSchedules } from '@desktop-client/hooks/useCachedSchedules';
|
||||
import { useCategories } from '@desktop-client/hooks/useCategories';
|
||||
@@ -280,11 +283,7 @@ export function TransactionListItem({
|
||||
<Text
|
||||
style={{
|
||||
...styles.tnum,
|
||||
...makeAmountFullStyle(amount, {
|
||||
positiveColor: theme.tableText,
|
||||
negativeColor: theme.tableText,
|
||||
zeroColor: theme.numberNeutral,
|
||||
}),
|
||||
...makeAmountFullStyle(amount),
|
||||
...textStyle,
|
||||
}}
|
||||
>
|
||||
@@ -296,11 +295,7 @@ export function TransactionListItem({
|
||||
fontSize: 11,
|
||||
fontWeight: '400',
|
||||
...styles.tnum,
|
||||
...makeAmountFullStyle(runningBalance, {
|
||||
positiveColor: theme.numberPositive,
|
||||
negativeColor: theme.numberNegative,
|
||||
zeroColor: theme.numberNeutral,
|
||||
}),
|
||||
...makeBalanceAmountStyle(runningBalance),
|
||||
}}
|
||||
>
|
||||
{integerToCurrency(runningBalance)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { send } from 'loot-core/platform/client/connection';
|
||||
import { computeSchedulePreviewTransactions } from 'loot-core/shared/schedules';
|
||||
@@ -47,9 +47,18 @@ export function usePreviewTransactions({
|
||||
} = useCachedSchedules();
|
||||
const [isLoading, setIsLoading] = useState(isSchedulesLoading);
|
||||
const [error, setError] = useState<Error | undefined>(undefined);
|
||||
const [runningBalances, setRunningBalances] = useState<
|
||||
Map<TransactionEntity['id'], IntegerAmount>
|
||||
>(new Map());
|
||||
|
||||
const [upcomingLength] = useSyncedPref('upcomingScheduledTransactionLength');
|
||||
|
||||
// We don't want to re-render if options changes.
|
||||
// Putting options in a ref will prevent that and
|
||||
// allow us to use the latest options on next render.
|
||||
const optionsRef = useRef(options);
|
||||
optionsRef.current = options;
|
||||
|
||||
const scheduleTransactions = useMemo(() => {
|
||||
if (isSchedulesLoading) {
|
||||
return [];
|
||||
@@ -100,6 +109,21 @@ export function usePreviewTransactions({
|
||||
const ungroupedTransactions = ungroupTransactions(withDefaults);
|
||||
setPreviewTransactions(ungroupedTransactions);
|
||||
|
||||
if (optionsRef.current?.calculateRunningBalances) {
|
||||
setRunningBalances(
|
||||
// We always use the bottom up calculation for preview transactions
|
||||
// because the hook controls the order of the transactions. We don't
|
||||
// need to provide a custom way for consumers to calculate the running
|
||||
// balances, at least as of writing.
|
||||
calculateRunningBalancesBottomUp(
|
||||
ungroupedTransactions,
|
||||
// Preview transactions are behaves like 'all' splits
|
||||
'all',
|
||||
optionsRef.current?.startingBalance,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
})
|
||||
@@ -115,24 +139,6 @@ export function usePreviewTransactions({
|
||||
};
|
||||
}, [scheduleTransactions, schedules, statuses, upcomingLength]);
|
||||
|
||||
const runningBalances = useMemo(() => {
|
||||
if (!options?.calculateRunningBalances) {
|
||||
return new Map<TransactionEntity['id'], IntegerAmount>();
|
||||
}
|
||||
|
||||
// We always use the bottom up calculation for preview transactions
|
||||
// because the hook controls the order of the transactions.
|
||||
return calculateRunningBalancesBottomUp(
|
||||
previewTransactions,
|
||||
'all',
|
||||
options?.startingBalance,
|
||||
);
|
||||
}, [
|
||||
previewTransactions,
|
||||
options?.calculateRunningBalances,
|
||||
options?.startingBalance,
|
||||
]);
|
||||
|
||||
const returnError = error || scheduleQueryError;
|
||||
return {
|
||||
previewTransactions,
|
||||
|
||||
@@ -72,15 +72,6 @@ describe('utility functions', () => {
|
||||
expect(looselyParseAmount('(1 500.99)')).toBe(-1500.99);
|
||||
});
|
||||
|
||||
test('looseParseAmount handles trailing whitespace', () => {
|
||||
expect(looselyParseAmount('1055 ')).toBe(1055);
|
||||
expect(looselyParseAmount('$1,055 ')).toBe(1055);
|
||||
expect(looselyParseAmount('$1,055.00 ')).toBe(1055);
|
||||
expect(looselyParseAmount(' $1,055 ')).toBe(1055);
|
||||
expect(looselyParseAmount('3.45 ')).toBe(3.45);
|
||||
expect(looselyParseAmount(' 3.45 ')).toBe(3.45);
|
||||
});
|
||||
|
||||
test('number formatting works with comma-dot format', () => {
|
||||
setNumberFormat({ format: 'comma-dot', hideFraction: false });
|
||||
let formatter = getNumberFormat().formatter;
|
||||
|
||||
@@ -550,8 +550,6 @@ export function looselyParseAmount(amount: string) {
|
||||
return v.replace(/[^0-9-]/g, '');
|
||||
}
|
||||
|
||||
amount = amount.trim();
|
||||
|
||||
if (amount.startsWith('(') && amount.endsWith(')')) {
|
||||
// Remove Unicode minus inside parentheses before converting to ASCII minus
|
||||
amount = amount.replace(/\u2212/g, '');
|
||||
|
||||
@@ -59,7 +59,12 @@ export function getLoginMethod(req) {
|
||||
(req.body || { loginMethod: null }).loginMethod &&
|
||||
config.get('allowedLoginMethods').includes(req.body.loginMethod)
|
||||
) {
|
||||
return req.body.loginMethod;
|
||||
const accountDb = getAccountDb();
|
||||
const activeRow = accountDb.first(
|
||||
'SELECT method FROM auth WHERE method = ? AND active = 1',
|
||||
[req.body.loginMethod],
|
||||
);
|
||||
if (activeRow) return req.body.loginMethod;
|
||||
}
|
||||
|
||||
//BY-PASS ANY OTHER CONFIGURATION TO ENSURE HEADER AUTH
|
||||
|
||||
@@ -121,6 +121,15 @@ app.post('/change-password', (req, res) => {
|
||||
const session = validateSession(req, res);
|
||||
if (!session) return;
|
||||
|
||||
if (getActiveLoginMethod() !== 'password') {
|
||||
res.status(403).send({
|
||||
status: 'error',
|
||||
reason: 'forbidden',
|
||||
details: 'password-auth-not-active',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = changePassword(req.body.password);
|
||||
|
||||
if (error) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import request from 'supertest';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { getAccountDb, getServerPrefs } from './account-db';
|
||||
import { getAccountDb, getLoginMethod, getServerPrefs } from './account-db';
|
||||
import { bootstrapPassword } from './accounts/password';
|
||||
import { handlers as app } from './app-account';
|
||||
|
||||
const ADMIN_ROLE = 'ADMIN';
|
||||
@@ -33,6 +34,119 @@ const clearServerPrefs = () => {
|
||||
getAccountDb().mutate('DELETE FROM server_prefs');
|
||||
};
|
||||
|
||||
const insertAuthRow = (method, active, extraData = null) => {
|
||||
getAccountDb().mutate(
|
||||
'INSERT INTO auth (method, display_name, extra_data, active) VALUES (?, ?, ?, ?)',
|
||||
[method, method, extraData, active],
|
||||
);
|
||||
};
|
||||
|
||||
const clearAuth = () => {
|
||||
getAccountDb().mutate('DELETE FROM auth');
|
||||
};
|
||||
|
||||
describe('/change-password', () => {
|
||||
let userId, sessionToken;
|
||||
|
||||
beforeEach(() => {
|
||||
userId = uuidv4();
|
||||
sessionToken = generateSessionToken();
|
||||
createUser(userId, 'testuser', ADMIN_ROLE);
|
||||
createSession(userId, sessionToken);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
deleteUser(userId);
|
||||
clearAuth();
|
||||
});
|
||||
|
||||
it('should return 401 if no session token is provided', async () => {
|
||||
const res = await request(app).post('/change-password').send({
|
||||
password: 'newpassword',
|
||||
});
|
||||
|
||||
expect(res.statusCode).toEqual(401);
|
||||
expect(res.body).toHaveProperty('status', 'error');
|
||||
expect(res.body).toHaveProperty('reason', 'unauthorized');
|
||||
});
|
||||
|
||||
it('should return 403 when active auth method is openid', async () => {
|
||||
insertAuthRow('openid', 1);
|
||||
|
||||
const res = await request(app)
|
||||
.post('/change-password')
|
||||
.set('x-actual-token', sessionToken)
|
||||
.send({ password: 'newpassword' });
|
||||
|
||||
expect(res.statusCode).toEqual(403);
|
||||
expect(res.body).toEqual({
|
||||
status: 'error',
|
||||
reason: 'forbidden',
|
||||
details: 'password-auth-not-active',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 when active method is password but password is empty', async () => {
|
||||
bootstrapPassword('oldpassword');
|
||||
|
||||
const res = await request(app)
|
||||
.post('/change-password')
|
||||
.set('x-actual-token', sessionToken)
|
||||
.send({ password: '' });
|
||||
|
||||
expect(res.statusCode).toEqual(400);
|
||||
expect(res.body).toEqual({ status: 'error', reason: 'invalid-password' });
|
||||
});
|
||||
|
||||
it('should return 200 when active method is password and new password is valid', async () => {
|
||||
bootstrapPassword('oldpassword');
|
||||
|
||||
const res = await request(app)
|
||||
.post('/change-password')
|
||||
.set('x-actual-token', sessionToken)
|
||||
.send({ password: 'newpassword' });
|
||||
|
||||
expect(res.statusCode).toEqual(200);
|
||||
expect(res.body).toEqual({ status: 'ok', data: {} });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLoginMethod()', () => {
|
||||
afterEach(() => {
|
||||
clearAuth();
|
||||
});
|
||||
|
||||
it('returns the active DB method when no req is provided', () => {
|
||||
insertAuthRow('password', 1);
|
||||
expect(getLoginMethod(undefined)).toBe('password');
|
||||
});
|
||||
|
||||
it('honors a client-requested method when it is active in DB', () => {
|
||||
insertAuthRow('openid', 1);
|
||||
const req = { body: { loginMethod: 'openid' } };
|
||||
expect(getLoginMethod(req)).toBe('openid');
|
||||
});
|
||||
|
||||
it('ignores a client-requested method that is inactive in DB', () => {
|
||||
insertAuthRow('openid', 1);
|
||||
insertAuthRow('password', 0);
|
||||
const req = { body: { loginMethod: 'password' } };
|
||||
expect(getLoginMethod(req)).toBe('openid');
|
||||
});
|
||||
|
||||
it('ignores a client-requested method that is not in DB', () => {
|
||||
insertAuthRow('openid', 1);
|
||||
const req = { body: { loginMethod: 'password' } };
|
||||
expect(getLoginMethod(req)).toBe('openid');
|
||||
});
|
||||
|
||||
it('falls back to config default when auth table is empty and no req', () => {
|
||||
// auth table is empty — getActiveLoginMethod() returns undefined
|
||||
// config default for loginMethod is 'password'
|
||||
expect(getLoginMethod(undefined)).toBe('password');
|
||||
});
|
||||
});
|
||||
|
||||
describe('/server-prefs', () => {
|
||||
describe('POST /server-prefs', () => {
|
||||
let adminUserId, basicUserId, adminSessionToken, basicSessionToken;
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Bugfixes
|
||||
authors: [LeviBorodenko]
|
||||
---
|
||||
|
||||
[Mobile] Show running balance on upcoming transactions when respective setting is toggled
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Bugfixes
|
||||
authors: [Juulz]
|
||||
---
|
||||
|
||||
Make mobile account page colors more consistent
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [jfdoming]
|
||||
---
|
||||
|
||||
Move migrations CI script to typescript + ci-actions
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Bugfixes
|
||||
authors: [mibragimov]
|
||||
---
|
||||
|
||||
Fix CSV import incorrectly parsing transaction amounts that contain trailing whitespace (e.g. amounts from Excel-saved CSV files).
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Establish centralized AI governance documentation for commit and pull request standards.
|
||||
6
upcoming-release-notes/7155.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Bugfixes
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Fixed a privilege escalation issue affecting password changes
|
||||
@@ -39,8 +39,6 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@actual-app/ci-actions@workspace:packages/ci-actions"
|
||||
dependencies:
|
||||
extensionless: "npm:^2.0.6"
|
||||
typescript: "npm:^5.9.3"
|
||||
vitest: "npm:^4.0.18"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -16033,13 +16031,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"extensionless@npm:^2.0.6":
|
||||
version: 2.0.6
|
||||
resolution: "extensionless@npm:2.0.6"
|
||||
checksum: 10/4a264600d9ff811534b35a66ff59eb075ca5c1ae4f25213bfa71d26a5e28ba5188a0d743f4e1dc8255cf9739258d5d374c6f757957faf4fe0d4ec5f57f51034f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"extract-zip@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "extract-zip@npm:2.0.1"
|
||||
|
||||