Skip to content

Commit 806bc96

Browse files
authored
fix(api): distinguish 401 (auth failure) from 403 (permissions) (#1226)
Backport of v1.x #1145 to main. Previously main returned the same "access denied / lacks required permissions" message for both 401 and 403, which was misleading when the user's real problem was a revoked/expired API token — it sent them chasing permissions instead of re-authenticating. Changes: * `utils/socket/api.mts` `getErrorMessageForHttpStatusCode` — split 401 and 403 into separate branches with distinct, actionable guidance (re-auth vs. check permissions). * `commands/scan/perform-reachability-analysis.mts` — when the enterprise-plan check fails with a 401, return "Authentication failed" + token-focused guidance instead of the generic "Unable to verify plan permissions" message. * Updated the matching unit test assertion for the 401 branch. Skipped from v1.x's version: the extra `logger.fail` in `fetch-organization-list.mts`. Main's `handleApiCall` already wires the cause into the returned CResult; a caller-level log would double-log in many paths.
1 parent 4c26d1d commit 806bc96

File tree

3 files changed

+24
-2
lines changed

3 files changed

+24
-2
lines changed

packages/cli/src/commands/scan/perform-reachability-analysis.mts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'node:path'
22

33
import { getDefaultLogger } from '@socketsecurity/lib/logger'
44

5+
import { HTTP_STATUS_UNAUTHORIZED } from '../../constants/http.mts'
56
import { DOT_SOCKET_DOT_FACTS_JSON } from '../../constants/paths.mts'
67
import {
78
SOCKET_DEFAULT_BRANCH,
@@ -81,6 +82,15 @@ export async function performReachabilityAnalysis(
8182
// Check if user has enterprise plan for reachability analysis.
8283
const orgsCResult = await fetchOrganization()
8384
if (!orgsCResult.ok) {
85+
const httpCode = (orgsCResult as { data?: { code?: number } }).data?.code
86+
if (httpCode === HTTP_STATUS_UNAUTHORIZED) {
87+
return {
88+
ok: false,
89+
message: 'Authentication failed',
90+
cause:
91+
'Your API token appears to be invalid, expired, or revoked. Please check your token and try again.',
92+
}
93+
}
8494
return {
8595
ok: false,
8696
message: 'Unable to verify plan permissions',

packages/cli/src/utils/socket/api.mts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,16 @@ export async function getErrorMessageForHttpStatusCode(code: number) {
163163
'💡 Try: Check your command syntax and parameter values.'
164164
)
165165
}
166-
if (code === HTTP_STATUS_FORBIDDEN || code === HTTP_STATUS_UNAUTHORIZED) {
166+
if (code === HTTP_STATUS_UNAUTHORIZED) {
167+
return (
168+
'❌ Authentication failed: Your Socket API token appears to be invalid, expired, or revoked.\n' +
169+
'💡 Try:\n' +
170+
' • Run `socket whoami` to verify your current token\n' +
171+
' • Run `socket login` to re-authenticate\n' +
172+
` • Manage tokens at ${SOCKET_SETTINGS_API_TOKENS_URL}`
173+
)
174+
}
175+
if (code === HTTP_STATUS_FORBIDDEN) {
167176
return (
168177
'❌ Access denied: Your API token lacks required permissions or organization access.\n' +
169178
'💡 Try:\n' +

packages/cli/test/unit/utils/socket/api.test.mts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ describe('api utilities', () => {
173173

174174
it('returns message for 401 Unauthorized', async () => {
175175
const result = await getErrorMessageForHttpStatusCode(401)
176-
expect(result).toContain('permissions')
176+
// 401 is now distinct from 403: it's an auth/token problem, not
177+
// a permissions problem. Callers get actionable "re-auth" guidance.
178+
expect(result).toContain('Authentication failed')
179+
expect(result).toContain('token')
177180
})
178181

179182
it('returns message for 403 Forbidden', async () => {

0 commit comments

Comments
 (0)