diff --git a/.github/workflows/test-wtih-vscode-build.yml b/.github/workflows/test-wtih-vscode-build.yml index 5bc2b0d10..cc4fc5409 100644 --- a/.github/workflows/test-wtih-vscode-build.yml +++ b/.github/workflows/test-wtih-vscode-build.yml @@ -9,7 +9,7 @@ on: - master jobs: - build: + build-with-vscode-build: strategy: matrix: os: [macos-14] diff --git a/functions/api/github-auth-callback.ts b/functions/api/github-auth-callback.ts index 34818e035..90f910f0f 100644 --- a/functions/api/github-auth-callback.ts +++ b/functions/api/github-auth-callback.ts @@ -18,10 +18,14 @@ const createResponseHtml = (text: string, script: string) => ` // return the data to the opener window by postMessage API, // and close current window if successfully connected -const createAuthorizeResultHtml = (data: Record, origins: string) => { +const createAuthorizeResultHtml = (data: Record, state: string, origins: string) => { const errorText = 'Failed! You can close this window and retry.'; const successText = 'Connected! You can now close this window.'; - const resultStr = `{ type: 'authorizing', payload: ${JSON.stringify(data)} }`; + const resultStr = JSON.stringify({ + type: 'authorizing', + payload: data, + state: state.replace(/[^a-zA-Z0-9]/g, ''), + }).replace(/ = async ({ request, env }) => { - const code = new URL(request.url).searchParams.get('code'); + const searchParams = new URL(request.url).searchParams; + const code = searchParams.get('code'); const createResponse = (status, data) => { - const body = createAuthorizeResultHtml(data, env.GITHUB1S_ALLOWED_ORIGINS); + const state = searchParams.get('state') || ''; + const body = createAuthorizeResultHtml(data, state, env.GITHUB1S_ALLOWED_ORIGINS); return new Response(body, { status, headers: { 'content-type': 'text/html' } }); }; diff --git a/functions/api/gitlab-auth-callback.ts b/functions/api/gitlab-auth-callback.ts index 46143f37d..e8c5bba5d 100644 --- a/functions/api/gitlab-auth-callback.ts +++ b/functions/api/gitlab-auth-callback.ts @@ -3,8 +3,6 @@ * @author netcon */ -const AUTH_REDIRECT_URI = 'https://auth.gitlab1s.com/api/gitlab-auth-callback'; - const createResponseHtml = (text: string, script: string) => ` @@ -20,10 +18,14 @@ const createResponseHtml = (text: string, script: string) => ` // return the data to the opener window by postMessage API, // and close current window if successfully connected -const createAuthorizeResultHtml = (data: Record, origins: string) => { +const createAuthorizeResultHtml = (data: Record, state: string, origins: string) => { const errorText = 'Failed! You can close this window and retry.'; const successText = 'Connected! You can now close this window.'; - const resultStr = `{ type: 'authorizing', payload: ${JSON.stringify(data)} }`; + const resultStr = JSON.stringify({ + type: 'authorizing', + payload: data, + state: state.replace(/[^a-zA-Z0-9]/g, ''), + }).replace(/ = async ({ request, env }) => { - const code = new URL(request.url).searchParams.get('code'); + const searchParams = new URL(request.url).searchParams; + const code = searchParams.get('code'); const createResponse = (status, data) => { - const body = createAuthorizeResultHtml(data, env.GITLAB1S_ALLOWED_ORIGINS); + const state = searchParams.get('state') || ''; + const body = createAuthorizeResultHtml(data, state, env.GITLAB1S_ALLOWED_ORIGINS); return new Response(body, { status, headers: { 'content-type': 'text/html' } }); }; @@ -65,7 +70,7 @@ export const onRequest: PagesFunction<{ code, client_id: env.GITLAB_OAUTH_ID, client_secret: env.GITLAB_OAUTH_SECRET, - redirect_uri: AUTH_REDIRECT_URI, + redirect_uri: env.GITLAB_OAUTH_REDIRECT_URI, grant_type: 'authorization_code', }), headers: { accept: 'application/json', 'content-type': 'application/json' }, diff --git a/scripts/build.js b/scripts/build.js index 5d424fc84..d19600d04 100755 --- a/scripts/build.js +++ b/scripts/build.js @@ -2,7 +2,6 @@ import path from 'path'; import fs from 'fs-extra'; -import cp from 'child_process'; import { executeCommand, PROJECT_ROOT } from './utils.js'; const main = () => { diff --git a/src/github-auth.ts b/src/github-auth.ts index 23b558122..86acf6daf 100644 --- a/src/github-auth.ts +++ b/src/github-auth.ts @@ -4,23 +4,22 @@ */ const GITHUB_ORIGIN = 'https://github.com'; -const AUTH_PAGE_ORIGIN = 'https://auth.github1s.com'; -const AUTH_REDIRECT_URI = `${AUTH_PAGE_ORIGIN}/api/github-auth-callback`; -const CLIENT_ID = 'eae6621348403ea49103'; +const OAUTH_REDIRECT_URI = `${location.origin}/api/github-auth-callback`; const OPEN_WINDOW_FEATURES = 'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,width=800,height=520,top=150,left=150'; -export const createRandomString = (length: number) => { - const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - return Array.from({ length }, () => charset.charAt(Math.floor(Math.random() * charset.length))).join(''); +export const createOAuthState = () => { + const bytes = new Uint8Array(16); + window.crypto.getRandomValues(bytes); + return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join(''); }; const createAuthorizeUrl = (state: string) => { const parameters = Object.entries({ state, scope: 'repo,user:email', - client_id: CLIENT_ID, - redirect_uri: AUTH_REDIRECT_URI, + client_id: GITHUB_OAUTH_ID, + redirect_uri: OAUTH_REDIRECT_URI, }).map(([key, value]) => `${key}=${encodeURIComponent(value)}`); return `${GITHUB_ORIGIN}/login/oauth/authorize?${parameters.join('&')}`; }; @@ -28,7 +27,7 @@ const createAuthorizeUrl = (state: string) => { export const timeout = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export const ConnectToGitHub = () => { - const STATE = createRandomString(32); + const STATE = createOAuthState(); const opener = window.open(createAuthorizeUrl(STATE), '_blank', OPEN_WINDOW_FEATURES); return new Promise((resolve) => { @@ -37,9 +36,10 @@ export const ConnectToGitHub = () => { // the user can be still open it from the tip. In this case, the `opener` // is null, and we should still process the authorizing message const isValidOpener = !!(opener && event.source === opener); - const isValidOrigin = event.origin === AUTH_PAGE_ORIGIN; + const isValidOrigin = event.origin === location.origin; const isValidResponse = event.data ? event.data.type === 'authorizing' : false; - if (!isValidOpener || !isValidOrigin || !isValidResponse) { + const isValidState = event.data ? event.data.state === STATE : false; + if (!isValidOpener || !isValidOrigin || !isValidResponse || !isValidState) { return; } window.removeEventListener('message', handleAuthMessage); diff --git a/src/gitlab-auth.ts b/src/gitlab-auth.ts index 6fe80a240..661579031 100644 --- a/src/gitlab-auth.ts +++ b/src/gitlab-auth.ts @@ -3,12 +3,10 @@ * @author netcon */ -import { timeout, createRandomString } from './github-auth'; +import { timeout, createOAuthState } from './github-auth'; const GITLAB_ORIGIN = 'https://gitlab.com'; -const AUTH_PAGE_ORIGIN = 'https://auth.gitlab1s.com'; -const AUTH_REDIRECT_URI = 'https://auth.gitlab1s.com/api/gitlab-auth-callback'; -const CLIENT_ID = '5ef142320efe9d2e8caeb0185771bb126d3035dc0a325c6ad5bab567f320d564'; +const OAUTH_REDIRECT_URI = `${location.origin}/api/gitlab-auth-callback`; const OPEN_WINDOW_FEATURES = 'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,width=800,height=520,top=150,left=150'; @@ -17,15 +15,15 @@ const createAuthorizeUrl = (state: string) => { state, scope: 'read_api', response_type: 'code', - client_id: CLIENT_ID, - redirect_uri: AUTH_REDIRECT_URI, + client_id: GITLAB_OAUTH_ID, + redirect_uri: OAUTH_REDIRECT_URI, }).map(([key, value]) => `${key}=${encodeURIComponent(value)}`); return `${GITLAB_ORIGIN}/oauth/authorize?${parameters.join('&')}`; }; // https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow export const ConnectToGitLab = async () => { - const STATE = createRandomString(32); + const STATE = createOAuthState(); const opener = window.open(createAuthorizeUrl(STATE), '_blank', OPEN_WINDOW_FEATURES); return new Promise((resolve) => { @@ -34,9 +32,10 @@ export const ConnectToGitLab = async () => { // the user can be still open it from the tip. In this case, the `opener` // is null, and we should still process the authorizing message const isValidOpener = !!(opener && event.source === opener); - const isValidOrigin = event.origin === AUTH_PAGE_ORIGIN; + const isValidOrigin = event.origin === location.origin; const isValidResponse = event.data ? event.data.type === 'authorizing' : false; - if (!isValidOpener || !isValidOrigin || !isValidResponse) { + const isValidState = event.data ? event.data.state === STATE : false; + if (!isValidOpener || !isValidOrigin || !isValidResponse || !isValidState) { return; } window.removeEventListener('message', handleAuthMessage); diff --git a/src/global.d.ts b/src/global.d.ts index db3ffabed..07f17951c 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -11,6 +11,8 @@ declare const GITHUB_ORIGIN: string; declare const GITLAB_ORIGIN: string; declare const GITHUB1S_EXTENSIONS: string; declare const AVAILABLE_LANGUAGES: string[]; +declare const GITHUB_OAUTH_ID: string; +declare const GITLAB_OAUTH_ID: string; /* eslint-disable no-var */ declare var dynamicImport: (url: string) => Promise; diff --git a/webpack.config.js b/webpack.config.js index 01a7315dc..1df4501a5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -87,6 +87,8 @@ export default (env, argv) => { GITLAB_ORIGIN: JSON.stringify(process.env.GITLAB_DOMAIN || 'https://gitlab.com'), GITHUB1S_EXTENSIONS: JSON.stringify(packUtils.getBuiltinExtensions(devVscode)), AVAILABLE_LANGUAGES: JSON.stringify(availableLanguages), + GITHUB_OAUTH_ID: JSON.stringify(process.env.GITHUB_OAUTH_ID || ''), + GITLAB_OAUTH_ID: JSON.stringify(process.env.GITLAB_OAUTH_ID || ''), }), ], performance: false,