Skip to content

Commit 4ac6635

Browse files
authored
Merge pull request #4 from OlaHulleberg/main
feat: add GitHub Enterprise support
2 parents 406a939 + 2d98129 commit 4ac6635

1 file changed

Lines changed: 102 additions & 11 deletions

File tree

index.mjs

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,27 @@
33
*/
44
export async function CopilotAuthPlugin({ client }) {
55
const CLIENT_ID = "Iv1.b507a08c87ecfe98";
6-
const DEVICE_CODE_URL = "https://github.com/login/device/code";
7-
const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
8-
const COPILOT_API_KEY_URL =
9-
"https://api.github.com/copilot_internal/v2/token";
106
const HEADERS = {
117
"User-Agent": "GitHubCopilotChat/0.35.0",
128
"Editor-Version": "vscode/1.99.3",
139
"Editor-Plugin-Version": "copilot-chat/0.35.0",
1410
"Copilot-Integration-Id": "vscode-chat",
1511
};
1612

13+
function normalizeDomain(url) {
14+
return url
15+
.replace(/^https?:\/\//, "")
16+
.replace(/\/$/, "");
17+
}
18+
19+
function getUrls(domain) {
20+
return {
21+
DEVICE_CODE_URL: `https://${domain}/login/device/code`,
22+
ACCESS_TOKEN_URL: `https://${domain}/login/oauth/access_token`,
23+
COPILOT_API_KEY_URL: `https://api.${domain}/copilot_internal/v2/token`,
24+
};
25+
}
26+
1727
return {
1828
auth: {
1929
provider: "github-copilot",
@@ -30,13 +40,25 @@ export async function CopilotAuthPlugin({ client }) {
3040
}
3141
}
3242

43+
// Set baseURL based on deployment type
44+
const enterpriseUrl = info.enterpriseUrl;
45+
const baseURL = enterpriseUrl
46+
? `https://copilot-api.${normalizeDomain(enterpriseUrl)}`
47+
: "https://api.githubcopilot.com";
48+
3349
return {
50+
baseURL,
3451
apiKey: "",
3552
async fetch(input, init) {
3653
const info = await getAuth();
3754
if (info.type !== "oauth") return {};
3855
if (!info.access || info.expires < Date.now()) {
39-
const response = await fetch(COPILOT_API_KEY_URL, {
56+
const domain = info.enterpriseUrl
57+
? normalizeDomain(info.enterpriseUrl)
58+
: "github.com";
59+
const urls = getUrls(domain);
60+
61+
const response = await fetch(urls.COPILOT_API_KEY_URL, {
4062
headers: {
4163
Accept: "application/json",
4264
Authorization: `Bearer ${info.refresh}`,
@@ -48,15 +70,19 @@ export async function CopilotAuthPlugin({ client }) {
4870

4971
const tokenData = await response.json();
5072

73+
const saveProviderID = info.enterpriseUrl
74+
? "github-copilot-enterprise"
75+
: "github-copilot";
5176
await client.auth.set({
5277
path: {
53-
id: provider.id,
78+
id: saveProviderID,
5479
},
5580
body: {
5681
type: "oauth",
5782
refresh: info.refresh,
5883
access: tokenData.token,
5984
expires: tokenData.expires_at * 1000,
85+
...(info.enterpriseUrl && { enterpriseUrl: info.enterpriseUrl }),
6086
},
6187
});
6288
info.access = tokenData.token;
@@ -99,10 +125,62 @@ export async function CopilotAuthPlugin({ client }) {
99125
},
100126
methods: [
101127
{
102-
label: "Login with GitHub",
103128
type: "oauth",
104-
authorize: async () => {
105-
const deviceResponse = await fetch(DEVICE_CODE_URL, {
129+
label: "Login with GitHub Copilot",
130+
prompts: [
131+
{
132+
type: "select",
133+
key: "deploymentType",
134+
message: "Select GitHub deployment type",
135+
options: [
136+
{
137+
label: "GitHub.com",
138+
value: "github.com",
139+
hint: "Public",
140+
},
141+
{
142+
label: "GitHub Enterprise",
143+
value: "enterprise",
144+
hint: "Data residency or self-hosted",
145+
},
146+
],
147+
},
148+
{
149+
type: "text",
150+
key: "enterpriseUrl",
151+
message: "Enter your GitHub Enterprise URL or domain",
152+
placeholder: "company.ghe.com or https://company.ghe.com",
153+
condition: (inputs) => inputs.deploymentType === "enterprise",
154+
validate: (value) => {
155+
if (!value) return "URL or domain is required";
156+
try {
157+
const url = value.includes("://")
158+
? new URL(value)
159+
: new URL(`https://${value}`);
160+
if (!url.hostname)
161+
return "Please enter a valid URL or domain";
162+
return undefined;
163+
} catch {
164+
return "Please enter a valid URL (e.g., company.ghe.com or https://company.ghe.com)";
165+
}
166+
},
167+
},
168+
],
169+
async authorize(inputs = {}) {
170+
const deploymentType = inputs.deploymentType || "github.com";
171+
172+
let domain = "github.com";
173+
let actualProvider = "github-copilot";
174+
175+
if (deploymentType === "enterprise") {
176+
const enterpriseUrl = inputs.enterpriseUrl;
177+
domain = normalizeDomain(enterpriseUrl);
178+
actualProvider = "github-copilot-enterprise";
179+
}
180+
181+
const urls = getUrls(domain);
182+
183+
const deviceResponse = await fetch(urls.DEVICE_CODE_URL, {
106184
method: "POST",
107185
headers: {
108186
Accept: "application/json",
@@ -114,14 +192,20 @@ export async function CopilotAuthPlugin({ client }) {
114192
scope: "read:user",
115193
}),
116194
});
195+
196+
if (!deviceResponse.ok) {
197+
throw new Error("Failed to initiate device authorization");
198+
}
199+
117200
const deviceData = await deviceResponse.json();
201+
118202
return {
119203
url: deviceData.verification_uri,
120204
instructions: `Enter code: ${deviceData.user_code}`,
121205
method: "auto",
122206
callback: async () => {
123207
while (true) {
124-
const response = await fetch(ACCESS_TOKEN_URL, {
208+
const response = await fetch(urls.ACCESS_TOKEN_URL, {
125209
method: "POST",
126210
headers: {
127211
Accept: "application/json",
@@ -141,12 +225,19 @@ export async function CopilotAuthPlugin({ client }) {
141225
const data = await response.json();
142226

143227
if (data.access_token) {
144-
return {
228+
const result = {
145229
type: "success",
146230
refresh: data.access_token,
147231
access: "",
148232
expires: 0,
149233
};
234+
235+
if (actualProvider === "github-copilot-enterprise") {
236+
result.provider = "github-copilot-enterprise";
237+
result.enterpriseUrl = domain;
238+
}
239+
240+
return result;
150241
}
151242

152243
if (data.error === "authorization_pending") {

0 commit comments

Comments
 (0)