@vigilbase/cli
Version:
Vigilbase CLI — security scanners, agent tools, and Vigilbase One from the terminal.
72 lines (71 loc) • 4.53 kB
JavaScript
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);
}