Skip to content

Commit 5249f64

Browse files
committed
fix auth
1 parent 17e84ff commit 5249f64

2 files changed

Lines changed: 61 additions & 10 deletions

File tree

packages/core/src/auth/auth-manager.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,33 @@ function checkRequiredPermissions (
3737
}
3838
}
3939

40-
function checkCachedPermissionsIfKnown (
40+
function failUnknownRequiredPermissions (
41+
required: string[],
42+
reason: string,
43+
cause?: unknown
44+
): never {
45+
throw new PermissionError(
46+
required,
47+
`Cannot verify required permissions: ${required.join(', ')}. ` +
48+
`${reason} Retry when validation is available or re-authenticate.`,
49+
{ cause }
50+
)
51+
}
52+
53+
function checkCachedPermissions (
4154
required: string[],
4255
existing: Credentials,
43-
logger?: Logger
56+
logger?: Logger,
57+
cause?: unknown
4458
): void {
4559
if (required.length === 0) return
4660
if (existing.permissions === undefined) {
4761
logger?.debug('auth.credentials.cachedPermissionsUnknown', { orgId: existing.organizationId })
48-
return
62+
failUnknownRequiredPermissions(
63+
required,
64+
'Token validation is unavailable and cached credentials do not include permission evidence.',
65+
cause
66+
)
4967
}
5068
checkRequiredPermissions(required, existing.permissions)
5169
}
@@ -97,11 +115,11 @@ export async function ensureAuthenticated (authConfig: AuthConfig, logger?: Logg
97115
throw err
98116
}
99117
// API unavailable, timed out, or served a non-JSON fallback: keep setup
100-
// idempotent by trusting the unexpired, origin-matching credentials. If
101-
// this credentials file already has a permission cache, still enforce
102-
// requiredPermissions against that known local state.
118+
// idempotent by trusting the unexpired, origin-matching credentials
119+
// only when no permissions are required, or when cached permissions
120+
// locally prove the requested access.
103121
logger?.warn('auth.credentials.validationUnavailable', { error: (err as Error).message })
104-
checkCachedPermissionsIfKnown(required, existing, logger)
122+
checkCachedPermissions(required, existing, logger, err)
105123
return existing
106124
}
107125
}
@@ -179,6 +197,13 @@ export async function ensureAuthenticated (authConfig: AuthConfig, logger?: Logg
179197
if (err instanceof InvalidCredentialsError || err instanceof PermissionError) {
180198
throw err
181199
}
200+
if (required.length > 0) {
201+
failUnknownRequiredPermissions(
202+
required,
203+
'Token validation is unavailable, so fresh OAuth credentials cannot be authorized locally.',
204+
err
205+
)
206+
}
182207
// API unavailable - store optimistically
183208
logger?.warn('Could not validate token. Storing credentials optimistically.', { consoleId: callback.consoleId })
184209
const creds: Credentials = {

packages/core/test/integration/auth/auth-manager.test.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ describe('ensureAuthenticated - requiredPermissions', () => {
452452
assert.strictEqual(execFileCalls.length, 0)
453453
})
454454

455-
it('trusts stored credentials with unknown cached permissions when validation is unavailable', async () => {
455+
it('rejects stored credentials with unknown cached permissions when validation is unavailable', async () => {
456456
const { saveCredentials } = await import('../../../src/auth/token-storage.js')
457457

458458
const creds: Credentials = {
@@ -472,13 +472,39 @@ describe('ensureAuthenticated - requiredPermissions', () => {
472472
}) as unknown as typeof fetch
473473

474474
const { ensureAuthenticated } = await import('../../../src/auth/auth-manager.js')
475-
const result = await ensureAuthenticated(authConfigWithPerms)
476475

477-
assert.deepStrictEqual(result, creds)
476+
await assert.rejects(
477+
ensureAuthenticated(authConfigWithPerms),
478+
/Cannot verify required permissions: nsolid:benchmark:run, nsolid:profile:read/
479+
)
478480
assert.strictEqual(fetchCalls, 1)
479481
assert.strictEqual(execFileCalls.length, 0)
480482
})
481483

484+
it('does not store fresh OAuth credentials when required permissions cannot be verified', { timeout: 10000 }, async () => {
485+
const { loadCredentials } = await import('../../../src/auth/token-storage.js')
486+
487+
let fetchCalls = 0
488+
globalThis.fetch = (async () => {
489+
fetchCalls++
490+
throw new Error('ECONNREFUSED')
491+
}) as unknown as typeof fetch
492+
493+
const { ensureAuthenticated } = await import('../../../src/auth/auth-manager.js')
494+
const promise = ensureAuthenticated(authConfigWithPerms)
495+
const rejection = assert.rejects(
496+
promise,
497+
/Cannot verify required permissions: nsolid:benchmark:run, nsolid:profile:read/
498+
)
499+
500+
const state = await pollForState(getStateFromExecFileCall)
501+
await sendCallback(8767, state)
502+
await rejection
503+
assert.strictEqual(fetchCalls, 1)
504+
assert.strictEqual(execFileCalls.length, 1)
505+
assert.strictEqual(loadCredentials(), null)
506+
})
507+
482508
it('does not store fresh OAuth credentials when validation reports missing required permissions', { timeout: 10000 }, async () => {
483509
const { loadCredentials } = await import('../../../src/auth/token-storage.js')
484510

0 commit comments

Comments
 (0)