Compare commits

...

3 Commits

Author SHA1 Message Date
github-actions[bot]
f34acd2927 [AI] sync-server: support non-RS256 OpenID ID tokens (#6524)
Auto-detect the ID-token signing algorithm from the IdP's discovery
metadata, with an optional ACTUAL_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG
override. RS256 is still preferred when advertised, so existing installs
are unaffected; IdPs that sign with ECDSA (ES256/ES384/ES512) or other
RSA variants now work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:13:45 +01:00
Matt Fiddaman
695fd0e7e0 fix bank sync account linking modal being disabled when relinking existing accounts (#7487)
* fix link account modal button disabled

* note
2026-04-18 22:25:28 +00:00
Matiss Janis Aboltins
9682f6d8c9 ci: disable fail-fast for Electron build workflows (#7547)
* [AI] Disable fail-fast for Electron build matrices

Prevents cancellation of in-progress platform builds when one fails, so
Windows/macOS/Linux results are all visible on a single run.

* Add release notes for PR #7547

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-18 21:32:21 +00:00
11 changed files with 130 additions and 1 deletions

View File

@@ -22,6 +22,7 @@ jobs:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
os:
- ubuntu-22.04

View File

@@ -26,6 +26,7 @@ concurrency:
jobs:
build:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-22.04

View File

@@ -20,6 +20,7 @@ concurrency:
jobs:
build:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-22.04

View File

@@ -128,7 +128,16 @@ export function SelectLinkedAccountsModal({
const localAccounts = allAccounts.filter(a => a.closed === 0);
const [draftLinkAccounts, setDraftLinkAccounts] = useState<
Map<string, 'linking' | 'unlinking'>
>(new Map());
>(() => {
const externalAccountIds = new Set(externalAccounts.map(a => a.account_id));
const initial = new Map<string, 'linking' | 'unlinking'>();
for (const acc of localAccounts) {
if (acc.account_id && externalAccountIds.has(acc.account_id)) {
initial.set(acc.account_id, 'linking');
}
}
return initial;
});
const [chosenAccounts, setChosenAccounts] = useState<Record<string, string>>(
() => {
return Object.fromEntries(

View File

@@ -64,6 +64,19 @@ export async function bootstrapOpenId(configParameter) {
return {};
}
export function pickIdTokenSignedResponseAlg(issuer, configParameter) {
if (configParameter.id_token_signed_response_alg) {
return configParameter.id_token_signed_response_alg;
}
const supported = issuer?.metadata?.id_token_signing_alg_values_supported;
if (Array.isArray(supported) && supported.length > 0) {
return supported.includes('RS256') ? 'RS256' : supported[0];
}
return 'RS256';
}
async function setupOpenIdClient(configParameter) {
const issuer =
typeof configParameter.issuer === 'string'
@@ -73,6 +86,12 @@ async function setupOpenIdClient(configParameter) {
authorization_endpoint: configParameter.issuer.authorization_endpoint,
token_endpoint: configParameter.issuer.token_endpoint,
userinfo_endpoint: configParameter.issuer.userinfo_endpoint,
...(Array.isArray(
configParameter.issuer.id_token_signing_alg_values_supported,
) && {
id_token_signing_alg_values_supported:
configParameter.issuer.id_token_signing_alg_values_supported,
}),
});
const client = new issuer.Client({
@@ -83,6 +102,10 @@ async function setupOpenIdClient(configParameter) {
configParameter.server_hostname,
).toString(),
validate_id_token: true,
id_token_signed_response_alg: pickIdTokenSignedResponseAlg(
issuer,
configParameter,
),
});
return client;

View File

@@ -0,0 +1,53 @@
import { describe, expect, it } from 'vitest';
import { pickIdTokenSignedResponseAlg } from './openid';
describe('pickIdTokenSignedResponseAlg', () => {
it('returns the explicit override when provided, even if discovery suggests otherwise', () => {
const issuer = {
metadata: { id_token_signing_alg_values_supported: ['RS256', 'ES384'] },
};
expect(
pickIdTokenSignedResponseAlg(issuer, {
id_token_signed_response_alg: 'ES512',
}),
).toBe('ES512');
});
it('prefers RS256 when it is among the advertised algorithms (backwards-compat)', () => {
const issuer = {
metadata: { id_token_signing_alg_values_supported: ['ES384', 'RS256'] },
};
expect(pickIdTokenSignedResponseAlg(issuer, {})).toBe('RS256');
});
it('falls back to the first advertised algorithm when RS256 is not offered', () => {
const issuer = {
metadata: { id_token_signing_alg_values_supported: ['ES384', 'ES256'] },
};
expect(pickIdTokenSignedResponseAlg(issuer, {})).toBe('ES384');
});
it('uses a lone ECDSA algorithm (the bug-report case)', () => {
const issuer = {
metadata: { id_token_signing_alg_values_supported: ['ES384'] },
};
expect(pickIdTokenSignedResponseAlg(issuer, {})).toBe('ES384');
});
it('defaults to RS256 when the issuer exposes no signing metadata', () => {
expect(pickIdTokenSignedResponseAlg({ metadata: {} }, {})).toBe('RS256');
expect(pickIdTokenSignedResponseAlg(undefined, {})).toBe('RS256');
});
it('treats an empty override as "auto-detect"', () => {
const issuer = {
metadata: { id_token_signing_alg_values_supported: ['ES384'] },
};
expect(
pickIdTokenSignedResponseAlg(issuer, {
id_token_signed_response_alg: '',
}),
).toBe('ES384');
});
});

View File

@@ -255,6 +255,12 @@ const configSchema = convict({
default: 'openid',
env: 'ACTUAL_OPENID_AUTH_METHOD',
},
id_token_signed_response_alg: {
doc: 'Algorithm used by the OpenID provider to sign ID tokens (e.g. RS256, ES256, ES384). Auto-detected from discovery metadata when empty.',
format: String,
default: '',
env: 'ACTUAL_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG',
},
},
token_expiration: {

View File

@@ -138,4 +138,21 @@ describe('config schema', () => {
authorizationEndpoint,
);
});
it('parses the ID token signing algorithm override from the environment', () => {
process.env.TEST_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG = 'ES384';
const testSchema = convict({
openId: {
id_token_signed_response_alg: {
doc: 'Algorithm used by the OpenID provider to sign ID tokens.',
format: String,
default: '',
env: 'TEST_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG',
},
},
});
expect(() => testSchema.validate()).not.toThrow();
expect(testSchema.get('openId.id_token_signed_response_alg')).toBe('ES384');
});
});

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [matt-fidd]
---
Fix bank sync account linking modal being disabled when relinking existing accounts

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
Disable fail-fast in Electron build workflows to allow all matrix jobs to complete independently.

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [MatissJanis]
---
Sync server: accept non-RS256 OpenID ID tokens. The ID-token signing algorithm is now auto-detected from the provider's discovery metadata (with an optional `ACTUAL_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG` override), fixing login against IdPs that sign with ECDSA (ES256/ES384/ES512) or other RSA variants.