fix(skatteverket): treat AGI kvittens gateway refusals as errors and name the connector operator (#2234)
* fix(skatteverket): treat AGI kvittens gateway refusals as errors and name the connector operator The AGI kvittens cron bucketed ACCESS_DENIED as apigw_config with a warn-once suppression because the APIGW client was known to lack the AGI hantera subscription in Utvecklarportalen (#963). With that subscription being put in place (#2226), a gateway refusal is a regression and belongs in the ordinary error path, so the bucket, its "known configuration gap" comment and the apigwConfig response field are gone. The connector-mode gateway-refusal message said "kontakta supporten"; hosted is now itself a Connect installation for the canary companies, so the message names the connector operator by host instead. Refs #2226 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159Mi1sTAHfUDZkSysntYz2 * fix(skatteverket): keep the ACCESS_DENIED code in the kvittens cron body Skeptic finding on PR #2234: the generic error path ran the gateway refusal through getErrorMessage, whose Swedish keyword heuristic misses the gateway wording and collapsed it into "Något gick fel". Echo the machine-readable code instead, as the expired_token and grant_revoked rows already do; the full guidance stays in the error log. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159Mi1sTAHfUDZkSysntYz2 --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
cb39cded81
commit
80b87c55fc
@@ -185,6 +185,10 @@ describe('skvRequestWithAuth: connector mode', () => {
|
||||
expect((e as SkatteverketAuthError).code).toBe('ACCESS_DENIED')
|
||||
const { message } = e as SkatteverketAuthError
|
||||
expect(message).toMatch(/connectorn/)
|
||||
// Points at the connector's operator by host, not at an ambiguous
|
||||
// "support": hosted is itself a Connect installation (#2226).
|
||||
expect(message).toMatch(/operatör \(app\.hosted\.example\)/)
|
||||
expect(message).not.toMatch(/supporten/)
|
||||
expect(message).not.toMatch(/SKATTEVERKET_APIGW_CLIENT_ID|Utvecklarportalen/)
|
||||
}
|
||||
})
|
||||
@@ -198,6 +202,8 @@ describe('skvRequestWithAuth: connector mode', () => {
|
||||
expect((e as SkatteverketAuthError).code).toBe('ACCESS_DENIED')
|
||||
const { message } = e as SkatteverketAuthError
|
||||
expect(message).toMatch(/connectorn/)
|
||||
expect(message).toMatch(/operatör \(app\.hosted\.example\)/)
|
||||
expect(message).not.toMatch(/supporten/)
|
||||
expect(message).not.toMatch(/Utvecklarportalen/)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -317,20 +317,33 @@ function apigwOrScopeMessage(url: string): string {
|
||||
|
||||
/**
|
||||
* Connector-mode variant of the gateway-refusal guidance: the APIGW client
|
||||
* and its subscriptions belong to the HOSTED broker (Arcim), so telling a
|
||||
* self-host operator to check SKATTEVERKET_APIGW_CLIENT_ID or visit
|
||||
* Utvecklarportalen points at knobs their instance does not have. The token
|
||||
* they hold was also minted by the broker, so the only local actions are
|
||||
* checking the connector status and contacting support.
|
||||
* and its subscriptions belong to the connector's OPERATOR (the broker the
|
||||
* instance routes through), so telling a self-host operator to check
|
||||
* SKATTEVERKET_APIGW_CLIENT_ID or visit Utvecklarportalen points at knobs
|
||||
* their instance does not have. The token they hold was also minted by the
|
||||
* broker, so the only local actions are checking the connector status and
|
||||
* contacting the operator. The message names the operator by the connector
|
||||
* host rather than saying "support": hosted is itself a Connect
|
||||
* installation for the canary companies (#2209), so "support" no longer
|
||||
* says whose (#2226).
|
||||
*/
|
||||
function connectorGatewayMessage(url: string): string {
|
||||
function connectorGatewayMessage(url: string, connectBaseUrl: string): string {
|
||||
return (
|
||||
`Skatteverkets API-gateway nekade anropet till tjänsten "${apiHintFromUrl(url)}" via connectorn. ` +
|
||||
'Detta är ett konfigurationsproblem på värdtjänstens sida (gateway-prenumeration eller scope), ' +
|
||||
'inte på din instans: kontakta supporten. Anslutningsläget syns på /api/connector/status.'
|
||||
`Felet ligger hos connectorns operatör (${connectorHost(connectBaseUrl)}), i gateway-prenumerationen ` +
|
||||
'eller scope-listan, inte på din instans: kontakta operatören. Anslutningsläget syns på /api/connector/status.'
|
||||
)
|
||||
}
|
||||
|
||||
/** The connector operator's host, for messages that have to say WHOM to contact. */
|
||||
function connectorHost(connectBaseUrl: string): string {
|
||||
try {
|
||||
return new URL(connectBaseUrl).host
|
||||
} catch {
|
||||
return connectBaseUrl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A genuine token-scope rejection: the stored access token predates a scope
|
||||
* the service now requires, and only a fresh consent can widen it.
|
||||
@@ -567,7 +580,7 @@ export async function skvRequestWithAuth(
|
||||
// so either verdict re-arms the banner the user just tried to clear.
|
||||
if (isApigwScopeContractError(text)) {
|
||||
throw new SkatteverketAuthError(
|
||||
connector ? connectorGatewayMessage(url) : apigwOrScopeMessage(url),
|
||||
connector ? connectorGatewayMessage(url, connector.baseUrl) : apigwOrScopeMessage(url),
|
||||
'ACCESS_DENIED'
|
||||
)
|
||||
}
|
||||
@@ -631,7 +644,7 @@ export async function skvRequestWithAuth(
|
||||
// Connector mode: the gateway client is the broker's, not the instance's.
|
||||
throw new SkatteverketAuthError(
|
||||
connector
|
||||
? connectorGatewayMessage(url)
|
||||
? connectorGatewayMessage(url, connector.baseUrl)
|
||||
: `Skatteverkets API-gateway nekade anropet till "${apiHintFromUrl(url)}". ` +
|
||||
'Kontrollera att din APIGW-klient (SKATTEVERKET_APIGW_CLIENT_ID) har ' +
|
||||
'prenumeration på denna tjänst i Utvecklarportalen.',
|
||||
@@ -649,7 +662,7 @@ export async function skvRequestWithAuth(
|
||||
// likely fix instead.
|
||||
if (!text) {
|
||||
if (connector) {
|
||||
throw new SkatteverketAuthError(connectorGatewayMessage(url), 'ACCESS_DENIED')
|
||||
throw new SkatteverketAuthError(connectorGatewayMessage(url, connector.baseUrl), 'ACCESS_DENIED')
|
||||
}
|
||||
const apiHint = apiHintFromUrl(url)
|
||||
throw new SkatteverketAuthError(
|
||||
@@ -717,7 +730,7 @@ export async function skvRequestWithAuth(
|
||||
// scope actually exists, so it can never be automatic.
|
||||
if (isApigwScopeContractError(text)) {
|
||||
throw new SkatteverketAuthError(
|
||||
connector ? connectorGatewayMessage(url) : apigwOrScopeMessage(url),
|
||||
connector ? connectorGatewayMessage(url, connector.baseUrl) : apigwOrScopeMessage(url),
|
||||
'ACCESS_DENIED'
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user