From 8922e13b71ce0dfd4e001b7d5f045c0cca9e551a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20Rimkevi=C4=8Dius?= Date: Thu, 23 Apr 2026 03:16:29 +0300 Subject: [PATCH] frontend: implement GitLab PKCE OAuth flow --- frontend/src/services/gitlab.js | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 frontend/src/services/gitlab.js diff --git a/frontend/src/services/gitlab.js b/frontend/src/services/gitlab.js new file mode 100644 index 0000000..6010a05 --- /dev/null +++ b/frontend/src/services/gitlab.js @@ -0,0 +1,60 @@ +const VERIFIER_KEY = 'gitlab_verifier'; +const HOST_KEY = 'gitlab_host'; + +async function generateCodeChallenge(verifier) { + const encoded = new TextEncoder().encode(verifier); + const digest = await crypto.subtle.digest('SHA-256', encoded); + return btoa(String.fromCharCode(...new Uint8Array(digest))) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); +} + +export async function startGitlabLogin(host, clientId) { + const bytes = crypto.getRandomValues(new Uint8Array(48)); + const verifier = Array.from(bytes) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); + + const challenge = await generateCodeChallenge(verifier); + + sessionStorage.setItem(VERIFIER_KEY, verifier); + sessionStorage.setItem(HOST_KEY, host); + + const redirectUri = `${window.location.origin}/#/auth/gitlab`; + const params = new URLSearchParams({ + client_id: clientId, + redirect_uri: redirectUri, + response_type: 'code', + scope: 'read_api', + code_challenge: challenge, + code_challenge_method: 'S256', + }); + + window.location.href = `https://${host}/oauth/authorize?${params}`; +} + +export async function exchangeGitlabCode(code) { + const verifier = sessionStorage.getItem(VERIFIER_KEY); + const host = sessionStorage.getItem(HOST_KEY); + + sessionStorage.removeItem(VERIFIER_KEY); + sessionStorage.removeItem(HOST_KEY); + + const redirectUri = `${window.location.origin}/#/auth/gitlab`; + const body = new URLSearchParams({ + client_id: process.env.GITLAB_CLIENT_ID, + code, + code_verifier: verifier, + grant_type: 'authorization_code', + redirect_uri: redirectUri, + }); + + const response = await fetch(`https://${host}/oauth/token`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body, + }); + + return response.json(); +}