UNPKG

@vigilbase/cli

Version:

Vigilbase CLI — security scanners, agent tools, and Vigilbase One from the terminal.

72 lines (71 loc) • 4.53 kB
import { DeviceAuthError } from './device-auth.js'; import { z } from 'zod'; import { DEFAULT_IDENTITY_ISSUER, parseOrigin, STAGING_IDENTITY_ISSUER, trustedIdentityIssuer, validateOneOrigin } from '../lib/config.js'; import { requestText, parseJsonResponse } from '../lib/network.js'; /** An issuer (discovered from One, or saved with credentials) is trusted only when its origin is exactly trustedIdentityIssuer(config). */ export function validateIdentityIssuer(raw, config) { const trusted = trustedIdentityIssuer(config); let origin; // Do not echo a malformed issuer: it can contain credentials. try { origin = parseOrigin(raw, 'The Identity issuer'); } catch { throw new Error('Untrusted Identity issuer: not an HTTP(S) origin.'); } if (origin === trusted) return origin; throw new Error(config.identityBaseUrl ? `Untrusted Identity issuer ${origin}: --auth-url / VIGILBASE_AUTH_URL is ${trusted}. Set it to the issuer for this One, or unset it to use ${DEFAULT_IDENTITY_ISSUER}.` : `Untrusted Identity issuer ${origin}: only ${DEFAULT_IDENTITY_ISSUER} is trusted by default. For staging, pass --auth-url ${STAGING_IDENTITY_ISSUER} or set VIGILBASE_AUTH_URL.`); } async function json(config, url) { const { response, text } = await requestText(config, url, { headers: { Accept: 'application/json', 'User-Agent': config.userAgent } }); return { response, body: parseJsonResponse(response, text) }; } export async function identityProvider(config, issuer) { issuer = validateIdentityIssuer(issuer, config); const { response, body } = await json(config, `${issuer}/.well-known/openid-configuration`); const data = z.object({ issuer: z.literal(issuer), token_endpoint: z.string(), device_authorization_endpoint: z.string(), userinfo_endpoint: z.string(), revocation_endpoint: z.string() }).safeParse(body); if (!response.ok || !data.success) throw new Error('Invalid Identity discovery response.'); const endpoint = (raw, paths) => { let url; try { url = new URL(raw); } catch { throw new Error('Invalid Identity endpoint.'); } ; if (url.origin !== issuer || url.username || url.password || url.search || url.hash || !paths.includes(url.pathname)) throw new Error('Untrusted Identity endpoint.'); return url.href; }; return { issuer, resource: validateOneOrigin(config.oneBaseUrl), tokenEndpoint: endpoint(data.data.token_endpoint, ['/api/auth/oauth2/token', '/token']), deviceEndpoint: endpoint(data.data.device_authorization_endpoint, ['/api/auth/device/code']), userinfoEndpoint: endpoint(data.data.userinfo_endpoint, ['/api/auth/oauth2/userinfo', '/userinfo']), revocationEndpoint: endpoint(data.data.revocation_endpoint, ['/api/auth/oauth2/revoke', '/revoke']) }; } /** * Older One deployments answer the protected-resource path with a sign-in * redirect or an HTML page; those mean "no Identity metadata" (legacy login). * JSON that is present but malformed or names another resource still aborts. */ export async function discoverAuthProvider(config) { const one = validateOneOrigin(config.oneBaseUrl); const { response, text } = await requestText(config, `${one}/.well-known/oauth-protected-resource`, { headers: { Accept: 'application/json', 'User-Agent': config.userAgent }, redirect: 'manual' }); if (response.type === 'opaqueredirect' || response.status === 0 || (response.status >= 300 && response.status < 400)) return undefined; if (response.status === 404) return undefined; if (response.status === 429) throw new DeviceAuthError('Discovery rate limit exceeded.', { status: 429, code: 'rate_limited' }); let body; if (response.ok) { const contentType = (response.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase(); if (contentType && contentType !== 'application/json' && !contentType.endsWith('+json')) return undefined; try { body = JSON.parse(text); } catch { return undefined; } } const metadata = z.object({ resource: z.literal(one), authorization_servers: z.array(z.string()).length(1) }).safeParse(body); if (!response.ok || !metadata.success) throw new Error('Invalid One protected-resource discovery response.'); const issuer = metadata.data.authorization_servers[0]; if (issuer === `${one}/api/auth`) return undefined; return identityProvider(config, issuer); }