Skip to content

Commit

Permalink
fix: gitlab private repo auth
Browse files Browse the repository at this point in the history
  • Loading branch information
conwnet committed Feb 23, 2024
1 parent 0800739 commit d6e5be3
Show file tree
Hide file tree
Showing 17 changed files with 1,238 additions and 281 deletions.
4 changes: 2 additions & 2 deletions api/github-auth-callback/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "github-auth-callback",
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"license": "MIT",
"private": true,
"dependencies": {
"got": "^11.8.5"
},
"devDependencies": {
"@vercel/node": "^2.9.10"
"@vercel/node": "^3.0.20"
}
}
592 changes: 518 additions & 74 deletions api/github-auth-callback/yarn.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions api/gitlab-auth-callback/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "vscode-unpkg",
"version": "1.0.0",
"main": "index.js",
"name": "gitlab-auth-callback",
"version": "0.0.0",
"main": "index.ts",
"license": "MIT",
"private": true,
"dependencies": {
"got": "^11.8.5"
},
"devDependencies": {
"@vercel/node": "^2.5.7"
"@vercel/node": "^3.0.20"
}
}
692 changes: 592 additions & 100 deletions api/gitlab-auth-callback/yarn.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions extensions/github1s/assets/pages/assets/github.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions extensions/github1s/assets/pages/assets/gitlab.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 0 additions & 8 deletions extensions/github1s/assets/pages/github1s-authentication.css
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,6 @@
margin-right: 8px;
}

.github-logo {
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" height="32" width="32"><path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path></svg>');
}

.gitlab-logo {
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 24" height="24" width="25"><path fill="%23E24329" d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"></path><path fill="%23FC6D26" d="m24.507 9.5-.034-.09a11.44 11.44 0 0 0-4.56 2.051l-7.447 5.632 4.742 3.584 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"></path><path fill="%23FCA326" d="m7.707 20.677 2.56 1.935 1.555 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935-4.743-3.584-4.755 3.584Z"></path><path fill="%23FC6D26" d="M5.01 11.461a11.43 11.43 0 0 0-4.56-2.05L.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 4.745-3.584-7.444-5.632Z"></path></svg>');
}

.authentication-detail {
display: flex;
flex-direction: column;
Expand Down
3 changes: 2 additions & 1 deletion extensions/github1s/assets/pages/github1s-authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ const AuthenticationButton = (props) => {
bridgeCommands.OAuthAuthenticate().then(() => setAuthenticating(false));
}, []);

const buttonLogoUrl = encodeURI(`${pageConfig.extensionUri}/${pageConfig.OAuthButtonLogo}`);
return html`
<button class="authentication-button" disabled="${authenticating}" onClick=${handleButtonClick} ...${props}>
<span class=${'auth-button-logo ' + pageConfig.OAuthButtonLogoClass}></span>
<span class=${'auth-button-logo'} style=${`background-image: url("${buttonLogoUrl}")`}></span>
<span>${pageConfig.OAuthButtonText}</span>
</button>
`;
Expand Down
10 changes: 10 additions & 0 deletions extensions/github1s/assets/pages/github1s-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ const PageFooter = () => {
[updateSgApiFirst]
);

useEffect(() => {
const handler = ({ data }) => {
if (data.type === 'use-sourcegraph-api-first-changed') {
setSgApiFirst(data.value);
}
};
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
}, []);

useEffect(() => {
updateSgApiFirst();
}, [updateSgApiFirst]);
Expand Down
5 changes: 3 additions & 2 deletions extensions/github1s/src/adapters/github1s/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export class GitHub1sAuthenticationView {
protected OAuthCommand = 'github1s.commands.vscode.connectToGitHub';
protected pageConfig: Record<string, unknown> = {
authenticationFormTitle: 'Authenticating to GitHub',
OAuthButtonLogoClass: 'github-logo',
OAuthButtonText: 'Connect to GitHub',
OAuthButtonLogo: 'assets/pages/assets/github.svg',
createTokenLink: 'https://github.com/settings/tokens/new?scopes=repo&description=GitHub1s',
rateLimitDocLink: 'https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting',
rateLimitDocLinkText: 'GitHub Rate limiting Documentation',
Expand Down Expand Up @@ -122,9 +122,10 @@ export class GitHub1sAuthenticationView {
vscode.Uri.joinPath(extensionContext.extensionUri, 'assets/pages/components.css').toString(),
vscode.Uri.joinPath(extensionContext.extensionUri, 'assets/pages/github1s-authentication.css').toString(),
];
const globalPageConfig = { ...this.pageConfig, extensionUri: extensionContext.extensionUri.toString() };
const scripts = [
'data:text/javascript;base64,' +
Buffer.from(`window.pageConfig=${JSON.stringify(this.pageConfig)};`).toString('base64'),
Buffer.from(`window.pageConfig=${JSON.stringify(globalPageConfig)};`).toString('base64'),
vscode.Uri.joinPath(extensionContext.extensionUri, 'assets/pages/github1s-authentication.js').toString(),
];

Expand Down
40 changes: 20 additions & 20 deletions extensions/github1s/src/adapters/github1s/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const detectErrorMessage = (response: any, authenticated: boolean) => {
if (response?.status === 403 && +response?.headers?.['x-ratelimit-remaining'] === 0) {
return errorMessages.rateLimited[authenticated ? 'authenticated' : 'anonymous'];
}
if (response?.status === 401 && +response?.data?.message?.includes?.('Bad credentials')) {
if (response?.status === 401 && response?.data?.message?.includes?.('Bad credentials')) {
return errorMessages.badCredentials[authenticated ? 'authenticated' : 'anonymous'];
}
if (response?.status === 404) {
Expand All @@ -55,6 +55,7 @@ export class GitHubFetcher {
private _emitter = new vscode.EventEmitter<boolean | null | undefined>();
private _originalRequest: Octokit['request'] | null = null;
public onDidChangeUseSourcegraphApiFirst = this._emitter.event;
private _currentRepoPromise: Promise<any> | null = null;

public request: Octokit['request'];
public graphql: Octokit['graphql'];
Expand All @@ -69,6 +70,7 @@ export class GitHubFetcher {
private constructor() {
this.initFetcherMethods();
GitHubTokenManager.getInstance().onDidChangeToken(() => this.initFetcherMethods());
GitHubTokenManager.getInstance().onDidChangeToken(() => this.checkCurrentRepo(true));
}

// initial fetcher methods in this way for correct `request/graphql` type inference
Expand All @@ -79,9 +81,9 @@ export class GitHubFetcher {
this._originalRequest = octokit.request;
this.request = Object.assign((...args: Parameters<Octokit['request']>) => {
return octokit.request(...args).catch(async (error) => {
const errorStatus = (error as any)?.response?.status;
const errorStatus = error?.response?.status as number | undefined;
const repoNotFound = errorStatus === 404 && !(await this.checkCurrentRepo());
if ([401, 403].includes(errorStatus) || repoNotFound) {
if ((errorStatus && [401, 403].includes(errorStatus)) || repoNotFound) {
// maybe we have to acquire github access token to continue
const message = detectErrorMessage(error?.response, !!accessToken);
await GitHub1sAuthenticationView.getInstance().open(message, true);
Expand All @@ -108,32 +110,30 @@ export class GitHubFetcher {
});
}

@decorate(memorize)
private async checkCurrentRepo() {
const [owner, repo] = (await this.getCurrentRepo()).split('/');
return this._originalRequest?.('GET /repos/{owner}/{repo}', { owner, repo }).then(
(response) => {
// turn off `useSourcegraphApiFirst` if current repository is private
response?.data?.private && this.setUseSourcegraphApiFirst(false);
return response?.data || null;
},
() => null
);
private checkCurrentRepo(forceUpdate: boolean = false) {
if (this._currentRepoPromise && !forceUpdate) {
return this._currentRepoPromise;
}
return (this._currentRepoPromise = Promise.resolve(this.getCurrentRepo()).then(async (repoFullName) => {
const [owner, repo] = repoFullName.split('/');
const response = await this._originalRequest?.('GET /repos/{owner}/{repo}', { owner, repo });
response?.data?.private && (await this.setUseSourcegraphApiFirst(false));
return response?.data || null;
})).then(() => null);
}

public async useSourcegraphApiFirst(repoFullName?: string): Promise<boolean> {
const targetRepo = repoFullName || (await this.getCurrentRepo());
public async useSourcegraphApiFirst(repo?: string): Promise<boolean> {
const targetRepo = repo || (await this.getCurrentRepo());
const globalState = getExtensionContext().globalState;
const cachedData: Record<string, boolean> | undefined = globalState.get(USE_SOURCEGRAPH_API_FIRST);
return !isNil(cachedData?.[targetRepo]) ? !!cachedData?.[targetRepo] : true;
}

public async setUseSourcegraphApiFirst(repoOrValue: string | boolean, value?: boolean) {
const targetRepo = !isNil(value) ? (repoOrValue as string) : await this.getCurrentRepo();
const targetValue = !isNil(value) ? value : !!repoOrValue;
public async setUseSourcegraphApiFirst(value: boolean, repo?: string) {
const targetRepo = repo || (await this.getCurrentRepo());
const globalState = getExtensionContext().globalState;
const cachedData: Record<string, boolean> | undefined = globalState.get(USE_SOURCEGRAPH_API_FIRST);
await globalState.update(USE_SOURCEGRAPH_API_FIRST, { ...cachedData, [targetRepo]: targetValue });
await globalState.update(USE_SOURCEGRAPH_API_FIRST, { ...cachedData, [targetRepo]: value });
this._emitter.fire(value);
}
}
5 changes: 3 additions & 2 deletions extensions/github1s/src/adapters/github1s/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class GitHub1sSettingsViewProvider implements vscode.WebviewViewProvider
webviewView.webview.postMessage({ type: 'token-changed', token });
});
this.apiFetcher.onDidChangeUseSourcegraphApiFirst((value) => {
webviewView.webview.postMessage({ type: 'use-sourcegraph-api-changed', value });
webviewView.webview.postMessage({ type: 'use-sourcegraph-api-first-changed', value });
});
}

Expand All @@ -96,9 +96,10 @@ export class GitHub1sSettingsViewProvider implements vscode.WebviewViewProvider
vscode.Uri.joinPath(extensionContext.extensionUri, 'assets/pages/components.css').toString(),
vscode.Uri.joinPath(extensionContext.extensionUri, 'assets/pages/github1s-settings.css').toString(),
];
const globalPageConfig = { ...this.pageConfig, extensionUri: extensionContext.extensionUri.toString() };
const scripts = [
'data:text/javascript;base64,' +
Buffer.from(`window.pageConfig=${JSON.stringify(this.pageConfig)};`).toString('base64'),
Buffer.from(`window.pageConfig=${JSON.stringify(globalPageConfig)};`).toString('base64'),
vscode.Uri.joinPath(extensionContext.extensionUri, 'assets/pages/github1s-settings.js').toString(),
];
webviewView.webview.html = createPageHtml(this.pageTitle, styles, scripts);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export class GitLab1sAuthenticationView extends GitHub1sAuthenticationView {
protected OAuthCommand = 'github1s.commands.vscode.connectToGitLab';
protected pageConfig = {
authenticationFormTitle: 'Authenticating to GitLab',
OAuthButtonLogoClass: 'gitlab-logo',
OAuthButtonText: 'Connect to GitLab',
OAuthButtonLogo: 'assets/pages/assets/gitlab.svg',
createTokenLink: 'https://gitlab.com/-/profile/personal_access_tokens?scopes=read_api&name=GitLab1s',
authenticationFeatures: [
{
Expand Down
66 changes: 34 additions & 32 deletions extensions/github1s/src/adapters/gitlab1s/data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,21 @@ const getPullState = (pull: { state: string; merged_at: string | null }): CodeRe
return CodeReviewState.Merged;
};

const resolveComputeAge = (timestamps: number[], ageLimit = 10) => {
const maxTimestamp = Math.max(...timestamps);
const minTimestamp = Math.min(...timestamps);
const step = (maxTimestamp - minTimestamp) / ageLimit;
return (timestamp: number) => {
const age = Math.floor((timestamp - minTimestamp) / (step || 1));
return (((Math.max(age, ageLimit - 1) % ageLimit) + ageLimit) % ageLimit) + 1;
};
};

const sourcegraphDataSource = SourcegraphDataSource.getInstance('gitlab');
const trySourcegraphApiFirst = (_target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value;

descriptor.value = async function <T extends (...args) => Promise<any>>(...args: Parameters<T>) {
// return originalMethod.apply(this, args);
const gitlabFetcher = GitLabFetcher.getInstance();
if (await gitlabFetcher.useSourcegraphApiFirst(args[0])) {
try {
Expand All @@ -73,7 +82,6 @@ export class GitLab1sDataSource extends DataSource {
private branchesPromiseMap: Map<string, Promise<Branch[]>> = new Map();
private tagsPromiseMap: Map<string, Promise<Tag[]>> = new Map();
private matchedRefsMap = new Map<string, string[]>();
private avatarPromiseMap = new Map<string, Promise<string>>();

public static getInstance(): GitLab1sDataSource {
if (GitLab1sDataSource.instance) {
Expand All @@ -82,16 +90,10 @@ export class GitLab1sDataSource extends DataSource {
return (GitLab1sDataSource.instance = new GitLab1sDataSource());
}

async provideRepository(repoFullName: string) {
if (!this.branchesPromiseMap.has(repoFullName)) {
await this.provideBranches(repoFullName);
}
return this.branchesPromiseMap.get(repoFullName)?.then((branches) => {
const defaultBranch = branches.find((br) => br.isDefault);
if (defaultBranch) {
return { name: defaultBranch.name, defaultBranch: defaultBranch?.name };
}
});
async provideRepository(repo: string) {
const fetcher = GitLabFetcher.getInstance();
const { data } = await fetcher.request('GET /projects/{repo}', { repo });
return { private: data.visibility === 'private', defaultBranch: data.default_branch };
}

@trySourcegraphApiFirst
Expand Down Expand Up @@ -395,26 +397,26 @@ export class GitLab1sDataSource extends DataSource {
requestParams
);
let startLine = 1;
return Promise.all(
(data || []).map(async ({ commit, lines }) => {
let startingLine = startLine;
let endingLine = startingLine + lines.length;
startLine = endingLine + 1;
return {
age: (+new Date(commit?.authored_date) % 10) as number,
startingLine,
endingLine,
commit: {
sha: commit?.id as string,
author: commit?.author_name as string,
email: commit?.author_email as string,
message: commit?.message as string,
createTime: new Date(commit?.authored_date),
avatarUrl: commit?.avatar_url || (await this.provideUserAvatarLink(commit?.author_name)),
},
};
})
);
const timestamps = data.map(({ commit }) => +new Date(commit.authored_date) || 0);
const computeAge = resolveComputeAge(timestamps);
return (data || []).map(({ commit, lines }) => {
let startingLine = startLine;
let endingLine = startingLine + lines.length;
startLine = endingLine + 1;
return {
age: computeAge(+new Date(commit?.authored_date) || 0),
startingLine,
endingLine,
commit: {
sha: commit?.id as string,
author: commit?.author_name as string,
email: commit?.author_email as string,
message: commit?.message as string,
createTime: new Date(commit?.authored_date),
avatarUrl: this.provideUserAvatarLink(encodeURIComponent(commit?.author?.name)),
},
};
});
}

async getAvatar(email): Promise<string> {
Expand Down
Loading

0 comments on commit d6e5be3

Please sign in to comment.