"Fossies" - the Fresh Open Source Software Archive

Member "cli-1.1280.1/src/cli/commands/auth/index.ts" (20 Feb 2024, 5485 Bytes) of package /linux/misc/snyk-cli-1.1280.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 import * as open from 'open';
    2 import { v4 as uuidv4 } from 'uuid';
    3 import * as Debug from 'debug';
    4 import { Spinner } from 'cli-spinner';
    5 import * as snyk from '../../../lib';
    6 import { verifyAPI } from './is-authed';
    7 import { isCI } from '../../../lib/is-ci';
    8 import { isDocker } from '../../../lib/is-docker';
    9 import { args as argsLib } from '../../args';
   10 import config from '../../../lib/config';
   11 import { makeRequest } from '../../../lib/request';
   12 import { CustomError } from '../../../lib/errors';
   13 import { AuthFailedError } from '../../../lib/errors';
   14 import { TokenExpiredError } from '../../../lib/errors/token-expired-error';
   15 import { MisconfiguredAuthInCI } from '../../../lib/errors/misconfigured-auth-in-ci-error';
   16 import { Payload } from '../../../lib/request/types';
   17 import { getQueryParamsAsString } from '../../../lib/query-strings';
   18 
   19 const apiUrl = new URL(config.API);
   20 // Ensure user gets redirected to the login page
   21 if (apiUrl.host.startsWith('api.')) {
   22   apiUrl.host = apiUrl.host.replace(/^api\./, 'app.');
   23 }
   24 const debug = Debug('snyk-auth');
   25 let attemptsLeft = 0;
   26 
   27 function resetAttempts() {
   28   attemptsLeft = isDocker() ? 60 : 3 * 60;
   29 }
   30 
   31 async function webAuth() {
   32   const token = uuidv4(); // generate a random key
   33 
   34   apiUrl.pathname = '/login';
   35   apiUrl.searchParams.append('token', token);
   36   let urlStr = apiUrl.toString();
   37 
   38   // It's not optimal, but I have to parse args again here. Alternative is reworking everything about how we parse args
   39   const args = [argsLib(process.argv).options];
   40   const utmParams = getQueryParamsAsString(args);
   41   if (utmParams) {
   42     urlStr += '&' + utmParams;
   43   }
   44 
   45   // suppress this message in CI
   46   if (!isCI()) {
   47     console.log(browserAuthPrompt(isDocker(), urlStr));
   48   } else {
   49     return Promise.reject(MisconfiguredAuthInCI());
   50   }
   51   const spinner = new Spinner('Waiting...');
   52   spinner.setSpinnerString('|/-\\');
   53 
   54   const ipFamily = await getIpFamily();
   55 
   56   try {
   57     spinner.start();
   58     if (!isDocker()) {
   59       await setTimeout(() => {
   60         open(urlStr);
   61       }, 0);
   62     }
   63 
   64     return await testAuthComplete(token, ipFamily);
   65   } finally {
   66     spinner.stop(true);
   67   }
   68 }
   69 
   70 async function testAuthComplete(
   71   token: string,
   72   ipFamily?: number,
   73 ): Promise<{ res; body }> {
   74   const payload: Partial<Payload> = {
   75     body: {
   76       token,
   77     },
   78     url: config.API + '/verify/callback',
   79     json: true,
   80     method: 'post',
   81   };
   82 
   83   if (ipFamily) {
   84     payload.family = ipFamily;
   85   }
   86 
   87   return new Promise((resolve, reject) => {
   88     debug(payload);
   89     makeRequest(payload, (error, res, body) => {
   90       debug(error, (res || {}).statusCode, body);
   91       if (error) {
   92         return reject(error);
   93       }
   94 
   95       if (res.statusCode !== 200) {
   96         return reject(errorForFailedAuthAttempt(res, body));
   97       }
   98 
   99       // we have success
  100       if (body.api) {
  101         return resolve({
  102           res,
  103           body,
  104         });
  105       }
  106 
  107       // we need to wait and poll again in a moment
  108       setTimeout(() => {
  109         attemptsLeft--;
  110         if (attemptsLeft > 0) {
  111           return resolve(testAuthComplete(token, ipFamily));
  112         }
  113 
  114         reject(TokenExpiredError());
  115       }, 1000);
  116     });
  117   });
  118 }
  119 
  120 export default async function auth(apiToken: string): Promise<string> {
  121   let promise;
  122   resetAttempts();
  123   if (apiToken) {
  124     // user is manually setting the API token on the CLI - let's trust them
  125     promise = verifyAPI(apiToken);
  126   } else {
  127     promise = webAuth();
  128   }
  129 
  130   return promise.then((data) => {
  131     const res = data.res;
  132     const body = res.body;
  133     debug(body);
  134 
  135     if (res.statusCode === 200 || res.statusCode === 201) {
  136       snyk.config.set('api', body.api);
  137       return (
  138         '\nYour account has been authenticated. Snyk is now ready to ' +
  139         'be used.\n'
  140       );
  141     }
  142     throw errorForFailedAuthAttempt(res, body);
  143   });
  144 }
  145 
  146 /**
  147  * Resolve an appropriate error for a failed attempt to authenticate
  148  *
  149  * @param res The response from the API
  150  * @param body The body of the failed authentication request
  151  */
  152 function errorForFailedAuthAttempt(res, body) {
  153   if (res.statusCode === 401 || res.statusCode === 403) {
  154     return AuthFailedError(body.userMessage, res.statusCode);
  155   } else {
  156     const userMessage = body && body.userMessage;
  157     const error = new CustomError(userMessage || 'Auth request failed');
  158     if (userMessage) {
  159       error.userMessage = userMessage;
  160     }
  161     error.code = res.statusCode;
  162     return error;
  163   }
  164 }
  165 
  166 async function getIpFamily(): Promise<6 | undefined> {
  167   const family = 6;
  168   try {
  169     // Dispatch a FORCED IPv6 request to test client's ISP and network capability
  170     await makeRequest({
  171       url: config.API + '/verify/callback',
  172       family, // family param forces the handler to dispatch a request using IP at "family" version
  173       method: 'post',
  174     });
  175     return family;
  176   } catch (e) {
  177     return undefined;
  178   }
  179 }
  180 
  181 function browserAuthPrompt(isDocker: boolean, urlStr: string): string {
  182   if (isDocker) {
  183     return (
  184       '\nTo authenticate your account, open the below URL in your browser.\n' +
  185       'After your authentication is complete, return to this prompt to ' +
  186       'start using Snyk.\n\n' +
  187       urlStr +
  188       '\n'
  189     );
  190   } else {
  191     return (
  192       '\nNow redirecting you to our auth page, go ahead and log in,\n' +
  193       "and once the auth is complete, return to this prompt and you'll\n" +
  194       "be ready to start using snyk.\n\nIf you can't wait use this url:\n" +
  195       urlStr +
  196       '\n'
  197     );
  198   }
  199 }