@zhafron/opencode-kiro-auth 1.6.6 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -8
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +13 -0
- package/dist/core/account/account-selector.js +0 -9
- package/dist/core/auth/auth-handler.js +25 -1
- package/dist/core/auth/idc-auth-method.d.ts +2 -3
- package/dist/core/auth/idc-auth-method.js +106 -167
- package/dist/core/auth/token-refresher.js +11 -0
- package/dist/core/request/error-handler.js +39 -21
- package/dist/infrastructure/database/account-repository.js +1 -0
- package/dist/kiro/oauth-idc.js +20 -6
- package/dist/plugin/accounts.js +37 -7
- package/dist/plugin/config/schema.d.ts +6 -0
- package/dist/plugin/config/schema.js +2 -0
- package/dist/plugin/logger.js +16 -1
- package/dist/plugin/request.js +4 -2
- package/dist/plugin/storage/migrations.js +41 -3
- package/dist/plugin/storage/sqlite.js +8 -7
- package/dist/plugin/sync/kiro-cli-profile.d.ts +1 -0
- package/dist/plugin/sync/kiro-cli-profile.js +30 -0
- package/dist/plugin/sync/kiro-cli.js +36 -9
- package/dist/plugin/token.js +4 -1
- package/dist/plugin/types.d.ts +2 -0
- package/dist/plugin/usage.js +19 -4
- package/package.json +1 -1
- package/dist/plugin/cli.d.ts +0 -8
- package/dist/plugin/cli.js +0 -103
- package/dist/plugin/server.d.ts +0 -34
- package/dist/plugin/server.js +0 -362
package/dist/plugin/cli.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { stdin as input, stdout as output } from 'node:process';
|
|
2
|
-
import { createInterface } from 'node:readline/promises';
|
|
3
|
-
export async function promptAddAnotherAccount(currentCount) {
|
|
4
|
-
const rl = createInterface({ input, output });
|
|
5
|
-
try {
|
|
6
|
-
const answer = await rl.question(`Add another account? (${currentCount} added) (y/n): `);
|
|
7
|
-
const normalized = answer.trim().toLowerCase();
|
|
8
|
-
return normalized === 'y' || normalized === 'yes';
|
|
9
|
-
}
|
|
10
|
-
finally {
|
|
11
|
-
rl.close();
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
export async function promptDeleteAccount(accounts) {
|
|
15
|
-
const rl = createInterface({ input, output });
|
|
16
|
-
try {
|
|
17
|
-
console.log(`\nSelect account(s) to delete:`);
|
|
18
|
-
for (const acc of accounts) {
|
|
19
|
-
const label = acc.email || `Account ${acc.index + 1}`;
|
|
20
|
-
console.log(` ${acc.index + 1}. ${label}`);
|
|
21
|
-
}
|
|
22
|
-
console.log(` 0. Cancel`);
|
|
23
|
-
console.log('');
|
|
24
|
-
while (true) {
|
|
25
|
-
const answer = await rl.question('Enter account number(s) (e.g., 1,2,3 or 1): ');
|
|
26
|
-
const trimmed = answer.trim();
|
|
27
|
-
if (trimmed === '0') {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
const parts = trimmed.split(',').map((s) => s.trim());
|
|
31
|
-
const numbers = [];
|
|
32
|
-
let invalid = false;
|
|
33
|
-
for (const part of parts) {
|
|
34
|
-
const num = parseInt(part, 10);
|
|
35
|
-
if (isNaN(num) || num < 1 || num > accounts.length) {
|
|
36
|
-
invalid = true;
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
if (!numbers.includes(num)) {
|
|
40
|
-
numbers.push(num);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
if (invalid) {
|
|
44
|
-
console.log(`Please enter valid numbers between 1 and ${accounts.length}, separated by commas`);
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (numbers.length === 0) {
|
|
48
|
-
console.log(`Please enter at least one account number`);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const indices = numbers.map((n) => n - 1);
|
|
52
|
-
const selectedAccounts = indices
|
|
53
|
-
.map((i) => accounts[i])
|
|
54
|
-
.filter((acc) => acc !== undefined);
|
|
55
|
-
if (selectedAccounts.length === 0) {
|
|
56
|
-
console.log(`No valid accounts selected`);
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
console.log(`\nYou are about to delete ${selectedAccounts.length} account(s):`);
|
|
60
|
-
for (const acc of selectedAccounts) {
|
|
61
|
-
const label = acc.email || `Account ${acc.index + 1}`;
|
|
62
|
-
console.log(` - ${label}`);
|
|
63
|
-
}
|
|
64
|
-
const confirm = await rl.question(`\nConfirm deletion? (y/n): `);
|
|
65
|
-
const normalized = confirm.trim().toLowerCase();
|
|
66
|
-
if (normalized === 'y' || normalized === 'yes') {
|
|
67
|
-
return selectedAccounts.map((acc) => acc.index);
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
finally {
|
|
73
|
-
rl.close();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
export async function promptLoginMode(existingAccounts) {
|
|
77
|
-
const rl = createInterface({ input, output });
|
|
78
|
-
try {
|
|
79
|
-
console.log(`\n${existingAccounts.length} account(s) saved:`);
|
|
80
|
-
for (const acc of existingAccounts) {
|
|
81
|
-
const label = acc.email || `Account ${acc.index + 1}`;
|
|
82
|
-
console.log(` ${acc.index + 1}. ${label}`);
|
|
83
|
-
}
|
|
84
|
-
console.log('');
|
|
85
|
-
while (true) {
|
|
86
|
-
const answer = await rl.question('(a)dd new account(s), (f)resh start, or (d)elete account? [a/f/d]: ');
|
|
87
|
-
const normalized = answer.trim().toLowerCase();
|
|
88
|
-
if (normalized === 'a' || normalized === 'add') {
|
|
89
|
-
return 'add';
|
|
90
|
-
}
|
|
91
|
-
if (normalized === 'f' || normalized === 'fresh') {
|
|
92
|
-
return 'fresh';
|
|
93
|
-
}
|
|
94
|
-
if (normalized === 'd' || normalized === 'delete') {
|
|
95
|
-
return 'delete';
|
|
96
|
-
}
|
|
97
|
-
console.log("Please enter 'a' to add accounts, 'f' to start fresh, or 'd' to delete account.");
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
rl.close();
|
|
102
|
-
}
|
|
103
|
-
}
|
package/dist/plugin/server.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { KiroRegion } from './types';
|
|
2
|
-
export interface KiroIDCTokenResult {
|
|
3
|
-
email: string;
|
|
4
|
-
accessToken: string;
|
|
5
|
-
refreshToken: string;
|
|
6
|
-
expiresAt: number;
|
|
7
|
-
clientId: string;
|
|
8
|
-
clientSecret: string;
|
|
9
|
-
}
|
|
10
|
-
export interface IDCAuthData {
|
|
11
|
-
verificationUrl: string;
|
|
12
|
-
verificationUriComplete: string;
|
|
13
|
-
userCode: string;
|
|
14
|
-
deviceCode: string;
|
|
15
|
-
clientId: string;
|
|
16
|
-
clientSecret: string;
|
|
17
|
-
interval: number;
|
|
18
|
-
expiresIn: number;
|
|
19
|
-
region: KiroRegion;
|
|
20
|
-
startUrl: string;
|
|
21
|
-
}
|
|
22
|
-
export declare function startIDCAuthServer(authData: IDCAuthData, startPort?: number, portRange?: number): Promise<{
|
|
23
|
-
url: string;
|
|
24
|
-
waitForAuth: () => Promise<KiroIDCTokenResult>;
|
|
25
|
-
}>;
|
|
26
|
-
/**
|
|
27
|
-
* Starts a local auth server that first shows a Start URL input page.
|
|
28
|
-
* After the user submits, it calls authorizeKiroIDC internally and transitions
|
|
29
|
-
* to the verification code page — no need to call authorizeKiroIDC beforehand.
|
|
30
|
-
*/
|
|
31
|
-
export declare function startIDCAuthServerWithInput(region: KiroRegion, defaultStartUrl: string | undefined, startPort?: number, portRange?: number): Promise<{
|
|
32
|
-
url: string;
|
|
33
|
-
waitForAuth: () => Promise<KiroIDCTokenResult>;
|
|
34
|
-
}>;
|
package/dist/plugin/server.js
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
|
-
import { authorizeKiroIDC } from '../kiro/oauth-idc';
|
|
3
|
-
import { getErrorHtml, getIDCAuthHtml, getStartUrlInputHtml, getSuccessHtml } from './auth-page';
|
|
4
|
-
import * as logger from './logger';
|
|
5
|
-
async function tryPort(port) {
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
const testServer = createServer();
|
|
8
|
-
testServer.once('error', () => resolve(false));
|
|
9
|
-
testServer.once('listening', () => {
|
|
10
|
-
testServer.close();
|
|
11
|
-
resolve(true);
|
|
12
|
-
});
|
|
13
|
-
testServer.listen(port, '127.0.0.1');
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
async function findAvailablePort(startPort, range) {
|
|
17
|
-
for (let i = 0; i < range; i++) {
|
|
18
|
-
const port = startPort + i;
|
|
19
|
-
const available = await tryPort(port);
|
|
20
|
-
if (available)
|
|
21
|
-
return port;
|
|
22
|
-
}
|
|
23
|
-
throw new Error(`No available ports in range ${startPort}-${startPort + range - 1}. Please close other applications using these ports.`);
|
|
24
|
-
}
|
|
25
|
-
export async function startIDCAuthServer(authData, startPort = 19847, portRange = 10) {
|
|
26
|
-
return new Promise(async (resolve, reject) => {
|
|
27
|
-
let port;
|
|
28
|
-
try {
|
|
29
|
-
port = await findAvailablePort(startPort, portRange);
|
|
30
|
-
logger.log(`Auth server will use port ${port}`);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
logger.error('Failed to find available port', error);
|
|
34
|
-
reject(error);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
let server = null;
|
|
38
|
-
let timeoutId = null;
|
|
39
|
-
let resolver = null;
|
|
40
|
-
let rejector = null;
|
|
41
|
-
const status = { status: 'pending' };
|
|
42
|
-
const cleanup = () => {
|
|
43
|
-
if (timeoutId)
|
|
44
|
-
clearTimeout(timeoutId);
|
|
45
|
-
if (server)
|
|
46
|
-
server.close();
|
|
47
|
-
};
|
|
48
|
-
const sendHtml = (res, html) => {
|
|
49
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
50
|
-
res.end(html);
|
|
51
|
-
};
|
|
52
|
-
const poll = async () => {
|
|
53
|
-
try {
|
|
54
|
-
const body = {
|
|
55
|
-
grantType: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
56
|
-
deviceCode: authData.deviceCode,
|
|
57
|
-
clientId: authData.clientId,
|
|
58
|
-
clientSecret: authData.clientSecret
|
|
59
|
-
};
|
|
60
|
-
const res = await fetch(`https://oidc.${authData.region}.amazonaws.com/token`, {
|
|
61
|
-
method: 'POST',
|
|
62
|
-
headers: { 'Content-Type': 'application/json' },
|
|
63
|
-
body: JSON.stringify(body)
|
|
64
|
-
});
|
|
65
|
-
const responseText = await res.text();
|
|
66
|
-
let d = {};
|
|
67
|
-
if (responseText) {
|
|
68
|
-
try {
|
|
69
|
-
d = JSON.parse(responseText);
|
|
70
|
-
}
|
|
71
|
-
catch (parseError) {
|
|
72
|
-
logger.error(`Auth polling error: Failed to parse JSON (status ${res.status})`, parseError);
|
|
73
|
-
throw parseError;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (res.ok) {
|
|
77
|
-
const acc = d.access_token || d.accessToken, ref = d.refresh_token || d.refreshToken, exp = Date.now() + (d.expires_in || d.expiresIn || 0) * 1000;
|
|
78
|
-
let email = 'builder-id@aws.amazon.com';
|
|
79
|
-
try {
|
|
80
|
-
// Derive user info URL from startUrl: replace /start with /api/user/info
|
|
81
|
-
const userInfoUrl = authData.startUrl.replace(/\/start\/?$/, '/api/user/info');
|
|
82
|
-
const infoRes = await fetch(userInfoUrl, {
|
|
83
|
-
headers: { Authorization: `Bearer ${acc}` }
|
|
84
|
-
});
|
|
85
|
-
if (infoRes.ok) {
|
|
86
|
-
const info = await infoRes.json();
|
|
87
|
-
email = info.email || info.userName || email;
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
logger.warn(`User info request failed with status ${infoRes.status}; using fallback email`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch (infoError) {
|
|
94
|
-
logger.warn(`Failed to fetch user info; using fallback email: ${infoError?.message || infoError}`);
|
|
95
|
-
}
|
|
96
|
-
status.status = 'success';
|
|
97
|
-
if (resolver)
|
|
98
|
-
resolver({
|
|
99
|
-
email,
|
|
100
|
-
accessToken: acc,
|
|
101
|
-
refreshToken: ref,
|
|
102
|
-
expiresAt: exp,
|
|
103
|
-
clientId: authData.clientId,
|
|
104
|
-
clientSecret: authData.clientSecret
|
|
105
|
-
});
|
|
106
|
-
setTimeout(cleanup, 2000);
|
|
107
|
-
}
|
|
108
|
-
else if (d.error === 'authorization_pending') {
|
|
109
|
-
setTimeout(poll, authData.interval * 1000);
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
status.status = 'failed';
|
|
113
|
-
status.error = d.error_description || d.error;
|
|
114
|
-
logger.error(`Auth polling failed a: ${status.error}`);
|
|
115
|
-
if (rejector)
|
|
116
|
-
rejector(new Error(status.error));
|
|
117
|
-
setTimeout(cleanup, 2000);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch (e) {
|
|
121
|
-
status.status = 'failed';
|
|
122
|
-
status.error = e.message;
|
|
123
|
-
logger.error(`Auth polling error b: ${e.message}`, e);
|
|
124
|
-
if (rejector)
|
|
125
|
-
rejector(e);
|
|
126
|
-
setTimeout(cleanup, 2000);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
server = createServer((req, res) => {
|
|
130
|
-
const u = req.url || '';
|
|
131
|
-
if (u === '/' || u.startsWith('/?'))
|
|
132
|
-
sendHtml(res, getIDCAuthHtml(authData.verificationUriComplete, authData.userCode, `http://127.0.0.1:${port}/status`));
|
|
133
|
-
else if (u === '/status') {
|
|
134
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
135
|
-
res.end(JSON.stringify(status));
|
|
136
|
-
}
|
|
137
|
-
else if (u === '/success')
|
|
138
|
-
sendHtml(res, getSuccessHtml());
|
|
139
|
-
else if (u === '/error')
|
|
140
|
-
sendHtml(res, getErrorHtml(status.error || 'Failed'));
|
|
141
|
-
else {
|
|
142
|
-
res.writeHead(404);
|
|
143
|
-
res.end();
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
server.on('error', (e) => {
|
|
147
|
-
logger.error(`Auth server error on port ${port}`, e);
|
|
148
|
-
cleanup();
|
|
149
|
-
reject(e);
|
|
150
|
-
});
|
|
151
|
-
server.listen(port, '127.0.0.1', () => {
|
|
152
|
-
timeoutId = setTimeout(() => {
|
|
153
|
-
status.status = 'timeout';
|
|
154
|
-
logger.warn('Auth timeout waiting for authorization');
|
|
155
|
-
if (rejector)
|
|
156
|
-
rejector(new Error('Timeout'));
|
|
157
|
-
cleanup();
|
|
158
|
-
}, 900000);
|
|
159
|
-
poll();
|
|
160
|
-
resolve({
|
|
161
|
-
url: `http://127.0.0.1:${port}`,
|
|
162
|
-
waitForAuth: () => new Promise((rv, rj) => {
|
|
163
|
-
resolver = rv;
|
|
164
|
-
rejector = rj;
|
|
165
|
-
})
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Starts a local auth server that first shows a Start URL input page.
|
|
172
|
-
* After the user submits, it calls authorizeKiroIDC internally and transitions
|
|
173
|
-
* to the verification code page — no need to call authorizeKiroIDC beforehand.
|
|
174
|
-
*/
|
|
175
|
-
export async function startIDCAuthServerWithInput(region, defaultStartUrl, startPort = 19847, portRange = 10) {
|
|
176
|
-
return new Promise(async (resolve, reject) => {
|
|
177
|
-
let port;
|
|
178
|
-
try {
|
|
179
|
-
port = await findAvailablePort(startPort, portRange);
|
|
180
|
-
logger.log(`Auth server (with input) will use port ${port}`);
|
|
181
|
-
}
|
|
182
|
-
catch (error) {
|
|
183
|
-
logger.error('Failed to find available port', error);
|
|
184
|
-
reject(error);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
let server = null;
|
|
188
|
-
let timeoutId = null;
|
|
189
|
-
let resolver = null;
|
|
190
|
-
let rejector = null;
|
|
191
|
-
const status = { status: 'pending' };
|
|
192
|
-
// authData is populated after the user submits the start URL form
|
|
193
|
-
let authData = null;
|
|
194
|
-
const cleanup = () => {
|
|
195
|
-
if (timeoutId)
|
|
196
|
-
clearTimeout(timeoutId);
|
|
197
|
-
if (server)
|
|
198
|
-
server.close();
|
|
199
|
-
};
|
|
200
|
-
const sendHtml = (res, html) => {
|
|
201
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
202
|
-
res.end(html);
|
|
203
|
-
};
|
|
204
|
-
const sendJson = (res, code, data) => {
|
|
205
|
-
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
206
|
-
res.end(JSON.stringify(data));
|
|
207
|
-
};
|
|
208
|
-
const poll = async (data) => {
|
|
209
|
-
try {
|
|
210
|
-
const body = {
|
|
211
|
-
grantType: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
212
|
-
deviceCode: data.deviceCode,
|
|
213
|
-
clientId: data.clientId,
|
|
214
|
-
clientSecret: data.clientSecret
|
|
215
|
-
};
|
|
216
|
-
const res = await fetch(`https://oidc.${data.region}.amazonaws.com/token`, {
|
|
217
|
-
method: 'POST',
|
|
218
|
-
headers: { 'Content-Type': 'application/json' },
|
|
219
|
-
body: JSON.stringify(body)
|
|
220
|
-
});
|
|
221
|
-
const responseText = await res.text();
|
|
222
|
-
let d = {};
|
|
223
|
-
if (responseText) {
|
|
224
|
-
try {
|
|
225
|
-
d = JSON.parse(responseText);
|
|
226
|
-
}
|
|
227
|
-
catch (parseError) {
|
|
228
|
-
logger.error(`Auth polling error: Failed to parse JSON (status ${res.status})`, parseError);
|
|
229
|
-
throw parseError;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (res.ok) {
|
|
233
|
-
const acc = d.access_token || d.accessToken, ref = d.refresh_token || d.refreshToken, exp = Date.now() + (d.expires_in || d.expiresIn || 0) * 1000;
|
|
234
|
-
let email = 'builder-id@aws.amazon.com';
|
|
235
|
-
try {
|
|
236
|
-
const userInfoUrl = data.startUrl.replace(/\/start\/?$/, '/api/user/info');
|
|
237
|
-
const infoRes = await fetch(userInfoUrl, {
|
|
238
|
-
headers: { Authorization: `Bearer ${acc}` }
|
|
239
|
-
});
|
|
240
|
-
if (infoRes.ok) {
|
|
241
|
-
const info = await infoRes.json();
|
|
242
|
-
email = info.email || info.userName || email;
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
logger.warn(`User info request failed with status ${infoRes.status}; using fallback email`);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
catch (infoError) {
|
|
249
|
-
logger.warn(`Failed to fetch user info; using fallback email: ${infoError?.message || infoError}`);
|
|
250
|
-
}
|
|
251
|
-
status.status = 'success';
|
|
252
|
-
if (resolver)
|
|
253
|
-
resolver({
|
|
254
|
-
email,
|
|
255
|
-
accessToken: acc,
|
|
256
|
-
refreshToken: ref,
|
|
257
|
-
expiresAt: exp,
|
|
258
|
-
clientId: data.clientId,
|
|
259
|
-
clientSecret: data.clientSecret
|
|
260
|
-
});
|
|
261
|
-
setTimeout(cleanup, 2000);
|
|
262
|
-
}
|
|
263
|
-
else if (d.error === 'authorization_pending') {
|
|
264
|
-
setTimeout(() => poll(data), data.interval * 1000);
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
status.status = 'failed';
|
|
268
|
-
status.error = d.error_description || d.error;
|
|
269
|
-
logger.error(`Auth polling failed: ${status.error}`);
|
|
270
|
-
if (rejector)
|
|
271
|
-
rejector(new Error(status.error));
|
|
272
|
-
setTimeout(cleanup, 2000);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
catch (e) {
|
|
276
|
-
status.status = 'failed';
|
|
277
|
-
status.error = e.message;
|
|
278
|
-
logger.error(`Auth polling error: ${e.message}`, e);
|
|
279
|
-
if (rejector)
|
|
280
|
-
rejector(e);
|
|
281
|
-
setTimeout(cleanup, 2000);
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
server = createServer(async (req, res) => {
|
|
285
|
-
const u = req.url || '';
|
|
286
|
-
// Step 1: Show start URL input page
|
|
287
|
-
if (u === '/' || u === '') {
|
|
288
|
-
sendHtml(res, getStartUrlInputHtml(defaultStartUrl || '', `http://127.0.0.1:${port}/setup`));
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
// Step 2: Receive start URL, call authorizeKiroIDC, return auth info to browser
|
|
292
|
-
if (u === '/setup' && req.method === 'POST') {
|
|
293
|
-
let body = '';
|
|
294
|
-
req.on('data', (chunk) => {
|
|
295
|
-
body += chunk;
|
|
296
|
-
});
|
|
297
|
-
req.on('end', async () => {
|
|
298
|
-
try {
|
|
299
|
-
const { startUrl } = JSON.parse(body);
|
|
300
|
-
const effectiveStartUrl = startUrl || undefined;
|
|
301
|
-
const data = await authorizeKiroIDC(region, effectiveStartUrl);
|
|
302
|
-
authData = data;
|
|
303
|
-
// Start polling now that we have device code
|
|
304
|
-
poll(authData);
|
|
305
|
-
sendJson(res, 200, {
|
|
306
|
-
userCode: data.userCode,
|
|
307
|
-
verificationUriComplete: data.verificationUriComplete
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
catch (e) {
|
|
311
|
-
logger.error('authorizeKiroIDC failed', e);
|
|
312
|
-
sendJson(res, 500, { error: e.message || 'Failed to initialize authentication' });
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
// Step 3: Show verification code page (browser redirects here after /setup)
|
|
318
|
-
if (u.startsWith('/auth')) {
|
|
319
|
-
const params = new URL(u, `http://127.0.0.1:${port}`).searchParams;
|
|
320
|
-
const code = params.get('code') || '';
|
|
321
|
-
const verUrl = params.get('url') || '';
|
|
322
|
-
sendHtml(res, getIDCAuthHtml(verUrl, code, `http://127.0.0.1:${port}/status`));
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
if (u === '/status') {
|
|
326
|
-
sendJson(res, 200, status);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
if (u === '/success') {
|
|
330
|
-
sendHtml(res, getSuccessHtml());
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
if (u.startsWith('/error')) {
|
|
334
|
-
sendHtml(res, getErrorHtml(status.error || 'Failed'));
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
res.writeHead(404);
|
|
338
|
-
res.end();
|
|
339
|
-
});
|
|
340
|
-
server.on('error', (e) => {
|
|
341
|
-
logger.error(`Auth server error on port ${port}`, e);
|
|
342
|
-
cleanup();
|
|
343
|
-
reject(e);
|
|
344
|
-
});
|
|
345
|
-
server.listen(port, '127.0.0.1', () => {
|
|
346
|
-
timeoutId = setTimeout(() => {
|
|
347
|
-
status.status = 'timeout';
|
|
348
|
-
logger.warn('Auth timeout waiting for authorization');
|
|
349
|
-
if (rejector)
|
|
350
|
-
rejector(new Error('Timeout'));
|
|
351
|
-
cleanup();
|
|
352
|
-
}, 900000);
|
|
353
|
-
resolve({
|
|
354
|
-
url: `http://127.0.0.1:${port}`,
|
|
355
|
-
waitForAuth: () => new Promise((rv, rj) => {
|
|
356
|
-
resolver = rv;
|
|
357
|
-
rejector = rj;
|
|
358
|
-
})
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
}
|