UNPKG

@withkeystone/cli

Version:

Keystone CLI - Test automation for modern web apps

113 lines 4.63 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeviceFlowAuthenticator = void 0; const chalk_1 = __importDefault(require("chalk")); const ora_1 = __importDefault(require("ora")); async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } class DeviceFlowAuthenticator { config; constructor(config) { this.config = config; } async authenticate() { // Request device code const deviceCodeData = await this.requestDeviceCode(); console.log('\nTo authenticate, visit:'); console.log(chalk_1.default.cyan(deviceCodeData.verification_uri)); console.log('\nAnd enter code: ' + chalk_1.default.yellow(deviceCodeData.user_code)); if (deviceCodeData.verification_uri_complete) { console.log('\nOr visit this URL directly:'); console.log(chalk_1.default.cyan(deviceCodeData.verification_uri_complete)); } // Poll for completion const spinner = (0, ora_1.default)('Waiting for authentication...').start(); try { const tokens = await this.pollForTokens(deviceCodeData.device_code, deviceCodeData.interval, deviceCodeData.expires_in); spinner.succeed('Authentication successful!'); return tokens; } catch (error) { spinner.fail('Authentication failed'); throw error; } } async requestDeviceCode() { const response = await fetch(`${this.config.apiUrl}/api/v1/cli/device/code`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: 'keystone-cli', scope: 'tests:read tests:write runs:read runs:write' }) }); if (!response.ok) { const error = await response.text(); throw new Error(`Failed to request device code: ${error}`); } return response.json(); } async pollForTokens(deviceCode, interval, expiresIn) { const maxAttempts = Math.floor(expiresIn / interval); const startTime = Date.now(); for (let i = 0; i < maxAttempts; i++) { // Wait for the specified interval if (i > 0) { await sleep(interval * 1000); } // Check if we've exceeded the expiry time const elapsed = (Date.now() - startTime) / 1000; if (elapsed > expiresIn) { throw new Error('Device code expired'); } try { const response = await fetch(`${this.config.apiUrl}/api/v1/cli/device/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code: deviceCode, client_id: 'keystone-cli' }) }); if (response.ok) { const data = await response.json(); return { access_token: data.access_token, refresh_token: data.refresh_token, expires_in: data.expires_in }; } // Check if it's an expected error const errorData = await response.json(); if (errorData.error === 'authorization_pending') { // Continue polling continue; } else if (errorData.error === 'slow_down') { // Increase polling interval interval = interval * 1.5; continue; } else { // Unexpected error throw new Error(errorData.error_description || errorData.error); } } catch (error) { // Network error or parsing error if (error instanceof Error && error.message.includes('authorization_pending')) { continue; } throw error; } } throw new Error('Authentication timeout'); } } exports.DeviceFlowAuthenticator = DeviceFlowAuthenticator; //# sourceMappingURL=DeviceFlowAuthenticator.js.map