mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-13 04:45:19 +00:00
Add explicit error message when OAuth scopes are incorrect
This commit is contained in:
parent
91caf129ed
commit
cbc2cfc190
3 changed files with 64 additions and 6 deletions
|
|
@ -1,11 +1,19 @@
|
|||
import * as Sentry from "@sentry/node";
|
||||
import { PrismaClient, AccountPermissionSyncJobStatus, Account} from "@sourcebot/db";
|
||||
import { PrismaClient, AccountPermissionSyncJobStatus, Account } from "@sourcebot/db";
|
||||
import { env, hasEntitlement, createLogger } from "@sourcebot/shared";
|
||||
import { Job, Queue, Worker } from "bullmq";
|
||||
import { Redis } from "ioredis";
|
||||
import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "../constants.js";
|
||||
import { createOctokitFromToken, getReposForAuthenticatedUser } from "../github.js";
|
||||
import { createGitLabFromOAuthToken, getProjectsForAuthenticatedUser } from "../gitlab.js";
|
||||
import {
|
||||
createOctokitFromToken,
|
||||
getOAuthScopesForAuthenticatedUser as getGitHubOAuthScopesForAuthenticatedUser,
|
||||
getReposForAuthenticatedUser,
|
||||
} from "../github.js";
|
||||
import {
|
||||
createGitLabFromOAuthToken,
|
||||
getOAuthScopesForAuthenticatedUser as getGitLabOAuthScopesForAuthenticatedUser,
|
||||
getProjectsForAuthenticatedUser,
|
||||
} from "../gitlab.js";
|
||||
import { Settings } from "../types.js";
|
||||
import { setIntervalAsync } from "../utils.js";
|
||||
|
||||
|
|
@ -163,6 +171,12 @@ export class AccountPermissionSyncer {
|
|||
token: account.access_token,
|
||||
url: env.AUTH_EE_GITHUB_BASE_URL,
|
||||
});
|
||||
|
||||
const scopes = await getGitHubOAuthScopesForAuthenticatedUser(octokit);
|
||||
if (!scopes.includes('repo')) {
|
||||
throw new Error(`OAuth token with scopes [${scopes.join(', ')}] is missing the 'repo' scope required for permission syncing.`);
|
||||
}
|
||||
|
||||
// @note: we only care about the private repos since we don't need to build a mapping
|
||||
// for public repos.
|
||||
// @see: packages/web/src/prisma.ts
|
||||
|
|
@ -189,6 +203,11 @@ export class AccountPermissionSyncer {
|
|||
url: env.AUTH_EE_GITLAB_BASE_URL,
|
||||
});
|
||||
|
||||
const scopes = await getGitLabOAuthScopesForAuthenticatedUser(api);
|
||||
if (!scopes.includes('read_api')) {
|
||||
throw new Error(`OAuth token with scopes [${scopes.join(', ')}] is missing the 'read_api' scope required for permission syncing.`);
|
||||
}
|
||||
|
||||
// @note: we only care about the private and internal repos since we don't need to build a mapping
|
||||
// for public repos.
|
||||
// @see: packages/web/src/prisma.ts
|
||||
|
|
|
|||
|
|
@ -197,6 +197,20 @@ export const getReposForAuthenticatedUser = async (visibility: 'all' | 'private'
|
|||
}
|
||||
}
|
||||
|
||||
// Gets oauth scopes
|
||||
// @see: https://github.com/octokit/auth-token.js/?tab=readme-ov-file#find-out-what-scopes-are-enabled-for-oauth-tokens
|
||||
export const getOAuthScopesForAuthenticatedUser = async (octokit: Octokit) => {
|
||||
try {
|
||||
const response = await octokit.request("HEAD /");
|
||||
const scopes = response.headers["x-oauth-scopes"]?.split(/,\s+/) || [];
|
||||
return scopes;
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
logger.error(`Failed to fetch OAuth scopes for authenticated user.`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const getReposOwnedByUsers = async (users: string[], octokit: Octokit, signal: AbortSignal, url?: string) => {
|
||||
const results = await Promise.allSettled(users.map((user) => githubQueryLimit(async () => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig) =
|
|||
const token = config.token ?
|
||||
await getTokenFromConfig(config.token) :
|
||||
hostname === GITLAB_CLOUD_HOSTNAME ?
|
||||
env.FALLBACK_GITLAB_CLOUD_TOKEN :
|
||||
undefined;
|
||||
env.FALLBACK_GITLAB_CLOUD_TOKEN :
|
||||
undefined;
|
||||
|
||||
const api = await createGitLabFromPersonalAccessToken({
|
||||
token,
|
||||
|
|
@ -202,7 +202,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig) =
|
|||
|
||||
return !isExcluded;
|
||||
});
|
||||
|
||||
|
||||
logger.debug(`Found ${repos.length} total repositories.`);
|
||||
|
||||
return {
|
||||
|
|
@ -311,4 +311,29 @@ export const getProjectsForAuthenticatedUser = async (visibility: 'private' | 'i
|
|||
logger.error(`Failed to fetch projects for authenticated user.`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetches OAuth scopes for the authenticated user.
|
||||
// @see: https://github.com/doorkeeper-gem/doorkeeper/wiki/API-endpoint-descriptions-and-examples#get----oauthtokeninfo
|
||||
// @see: https://docs.gitlab.com/api/oauth2/#retrieve-the-token-information
|
||||
export const getOAuthScopesForAuthenticatedUser = async (api: InstanceType<typeof Gitlab>) => {
|
||||
try {
|
||||
const response = await api.requester.get('/oauth/token/info');
|
||||
console.log('response', response);
|
||||
if (
|
||||
response &&
|
||||
typeof response.body === 'object' &&
|
||||
response.body !== null &&
|
||||
'scope' in response.body &&
|
||||
Array.isArray(response.body.scope)
|
||||
) {
|
||||
return response.body.scope;
|
||||
}
|
||||
|
||||
throw new Error('/oauth/token_info response body is not in the expected format.');
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
logger.error('Failed to fetch OAuth scopes for authenticated user.', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue