diff --git a/src/auth.ts b/src/auth.ts index c6d2cf3..68b414c 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -19,6 +19,7 @@ type OrgInfo = { name: string image: string | null plan: 'opensource' | 'team' | 'enterprise' + slug: string } type OrganizationsRecord = { @@ -42,6 +43,16 @@ async function getOrganizations(apiKey: string): Promise() + +function getDefaultOrg(organizations: OrganizationsRecord): OrgInfo | null { + const org = Object.values(organizations.organizations)[0] + if (!org || !org.slug) { + return null + } + return org +} + export async function activate(context: vscode.ExtensionContext, disposables: Array) { //#region file path/watching // responsible for watching files to know when to sync from disk @@ -103,12 +114,13 @@ export async function activate(context: vscode.ExtensionContext, disposables: Ar const sessionOnDisk: typeof liveSessions = new Map() if (typeof apiKey === 'string' && apiKey.length > 0 && apiKey !== SOCKET_PUBLIC_API_TOKEN) { const organizations = await getOrganizations(apiKey) - const org = Object.values(organizations!.organizations)[0] - if (org) { + const defaultOrg = organizations ? getDefaultOrg(organizations) : null + if (defaultOrg) { sessionOnDisk.set( apiKey, - sessionFromAPIKey(apiKey, org) + sessionFromAPIKey(apiKey, defaultOrg) ) + orgSlugByApiKey.set(apiKey, defaultOrg.slug) } } let added: Array = [] @@ -156,23 +168,41 @@ export async function activate(context: vscode.ExtensionContext, disposables: Ar return Array.from(liveSessions.values()) }, async createSession(scopes: readonly string[], options: vscode.AuthenticationProviderSessionOptions): Promise { - let organizations: OrganizationsRecord + let organizations: OrganizationsRecord | null = null + let defaultOrg: OrgInfo | null = null let apiKey: string = await vscode.window.showInputBox({ title: 'Socket Security API Token', placeHolder: 'Leave this blank to stay logged out', ignoreFocusOut: true, prompt: 'Enter your API token from https://socket.dev/', async validateInput(value) { - if (!value) return - organizations = (await getOrganizations(value))! - if (!organizations) return 'Invalid API key' + if (!value) { + return + } + + organizations = await getOrganizations(value) + if (!organizations) { + return 'Invalid API key' + } + + defaultOrg = getDefaultOrg(organizations) + if (!defaultOrg) { + return 'No organizations found for API key' + } } }) ?? '' if (!apiKey) { throw new Error('User did not want to provide an API key') } - const org = Object.values(organizations!.organizations)[0] - const session = sessionFromAPIKey(apiKey, org) + if (!organizations) { + organizations = await getOrganizations(apiKey) + } + defaultOrg = defaultOrg ?? (organizations ? getDefaultOrg(organizations) : null) + if (!defaultOrg) { + throw new Error('No organizations found for API key') + } + const session = sessionFromAPIKey(apiKey, defaultOrg) + orgSlugByApiKey.set(apiKey, defaultOrg.slug) let oldSessions = Array.from(liveSessions.values()) await syncLiveSessionToDisk(session) liveSessions = new Map([ @@ -195,6 +225,7 @@ export async function activate(context: vscode.ExtensionContext, disposables: Ar fs.unlinkSync(settingsPath) } catch {} if (session) { + orgSlugByApiKey.delete(session.accessToken) diskSessionsChanges.fire({ added: [], changed: [], @@ -238,6 +269,24 @@ export async function getAPIKey() { } } +export async function getOrgSlug(apiKey?: string) { + const resolvedApiKey = apiKey ?? await getAPIKey() + if (!resolvedApiKey || resolvedApiKey === SOCKET_PUBLIC_API_TOKEN) { + return null + } + const cached = orgSlugByApiKey.get(resolvedApiKey) + if (cached) { + return cached + } + const organizations = await getOrganizations(resolvedApiKey) + const defaultOrg = organizations ? getDefaultOrg(organizations) : null + if (!defaultOrg) { + return null + } + orgSlugByApiKey.set(resolvedApiKey, defaultOrg.slug) + return defaultOrg.slug +} + export function getAuthHeader(apiKey: string) { return `Bearer ${apiKey}` } @@ -256,4 +305,3 @@ function sessionFromAPIKey(apiKey: string, org: OrgInfo) { scopes: [], } } - diff --git a/src/ui/purl-alerts-and-scores/manager.ts b/src/ui/purl-alerts-and-scores/manager.ts index 1cc34b5..7d8bb8d 100644 --- a/src/ui/purl-alerts-and-scores/manager.ts +++ b/src/ui/purl-alerts-and-scores/manager.ts @@ -7,7 +7,7 @@ import logger from '../../infra/log' import os from 'os' import path from 'path' import fs from 'fs' -import { getAuthHeader, getAPIKey } from '../../auth' +import { getAuthHeader, getAPIKey, getOrgSlug } from '../../auth' // if this is updated update lifecycle scripts const cacheDir = path.resolve(os.homedir(), '.socket', 'vscode') @@ -160,7 +160,13 @@ export class PURLDataCache { bailPendingCacheEntries() return } - const req = https.request(`https://api.socket.dev/v0/purl?alerts=true&compact=false'`, { + const orgSlug = await getOrgSlug(apiKey) + if (!orgSlug) { + bailPendingCacheEntries(new Error('No organization available for API token')) + return + } + const encodedOrgSlug = encodeURIComponent(orgSlug) + const req = https.request(`https://api.socket.dev/v0/orgs/${encodedOrgSlug}/purl?alerts=true&compact=false`, { method: 'POST', headers: { 'content-type': 'application/json',