All files / src/entities/context connection-check.ts

100% Statements 29/29
100% Branches 20/20
100% Functions 4/4
100% Lines 27/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117            23x 23x 23x 23x 23x 23x                                           23x                                                                 23x 15x 15x       15x       30x 15x 15x 15x 30x 28x 28x         15x 15x   15x 15x 1x     15x 1x     1x   15x                              
/**
 * Connection check for the settings page: the account, whether GitLab answers, the
 * restrictions in effect, and what to do about problems. It reports no token and no
 * other user's data.
 */
 
import { GITLAB_BASE_URL } from '../../config';
import { currentCaller, getConfigurationService, resolveCaller } from '../../configuration';
import { listedToolSchema, scopeLabel } from '../../configuration/policy';
import { getTokenContext } from '../../oauth/token-context';
import { isToolAvailableForScopes } from '../../services/TokenScopeDetector';
import { executeWhoami } from './whoami';
 
export interface ConnectionCheck {
  account: string;
  instance: string;
  gitlabVersion: string;
  tier: string;
  /** Whether GitLab identified the account with the current credentials. */
  authenticated: boolean;
  /**
   * Whether GitLab answered the account lookup. When it did not, `authenticated` is false
   * without the credentials having been refused.
   */
  reachable: boolean;
  readOnly: boolean;
  preset: string | null;
  scope: string | null;
  availableTools: number;
  warnings: string[];
  recommendations: string[];
}
 
export const CONNECTION_CHECK_OUTPUT_SCHEMA = {
  type: 'object' as const,
  properties: {
    account: { type: 'string' },
    instance: { type: 'string' },
    gitlabVersion: { type: 'string' },
    tier: { type: 'string' },
    authenticated: { type: 'boolean' },
    reachable: { type: 'boolean' },
    readOnly: { type: 'boolean' },
    preset: { type: ['string', 'null'] },
    scope: { type: ['string', 'null'] },
    availableTools: { type: 'number' },
    warnings: { type: 'array', items: { type: 'string' } },
    recommendations: { type: 'array', items: { type: 'string' } },
  },
  required: [
    'account',
    'instance',
    'gitlabVersion',
    'tier',
    'authenticated',
    'reachable',
    'readOnly',
    'preset',
    'scope',
    'availableTools',
    'warnings',
    'recommendations',
  ],
  additionalProperties: false,
};
 
export async function checkConnection(): Promise<ConnectionCheck> {
  const caller = currentCaller() ?? resolveCaller(undefined, GITLAB_BASE_URL);
  const [whoami, resolved] = await Promise.all([
    executeWhoami(),
    getConfigurationService().resolve(caller),
  ]);
  const scope = resolved.policy.scope;
  // What this caller can call, as tools/list shows it: the instance's tools narrowed by
  // the OAuth token's scopes and by the caller's own settings, including a tool left
  // without any action the preset allows.
  const { RegistryManager } = await import('../../registry-manager');
  const registry = RegistryManager.getInstance();
  const tokenScopes = getTokenContext()?.gitlabScopes;
  const availableTools = registry.getAvailableToolNames(caller.instanceUrl).filter((name) => {
    if (tokenScopes !== undefined && !isToolAvailableForScopes(name, tokenScopes)) return false;
    const tool = registry.getTool(name, caller.instanceUrl);
    return (
      tool !== null &&
      listedToolSchema(resolved.policy, registry.getToolFacts(name), tool.inputSchema) !== undefined
    );
  }).length;
  const warnings = [...whoami.warnings];
  const recommendations = whoami.recommendations.map((r) => r.message);
  // This check runs also while GitLab is marked unreachable: an outage is not a refused sign-in.
  const reachable = whoami.userLookup !== 'failed';
  if (!reachable)
    warnings.push(
      'GitLab did not answer the account lookup, so the sign-in could not be checked; try again in a moment.',
    );
  if (resolved.presetUnavailable) {
    warnings.push(
      `The saved preset '${resolved.presetUnavailable}' no longer exists on this server; tools that change GitLab are off until another preset is chosen.`,
    );
    recommendations.push('Choose another preset in the GitLab settings, or with update_settings.');
  }
  return {
    account: whoami.user?.username ?? caller.accountLabel,
    instance: whoami.server.apiUrl,
    gitlabVersion: whoami.server.version,
    tier: whoami.server.tier,
    authenticated: whoami.user !== null,
    reachable,
    readOnly: whoami.server.readOnlyMode || resolved.policy.readOnly,
    preset: resolved.policy.presetName ?? null,
    scope: scopeLabel(scope) ?? null,
    availableTools,
    warnings,
    recommendations,
  };
}