aiag-cli 1.7.1 → 2.1.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 +125 -88
- package/dist/api/client.d.ts +170 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +513 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/endpoints.d.ts +112 -0
- package/dist/api/endpoints.d.ts.map +1 -0
- package/dist/api/endpoints.js +150 -0
- package/dist/api/endpoints.js.map +1 -0
- package/dist/api/types.d.ts +395 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +7 -0
- package/dist/api/types.js.map +1 -0
- package/dist/auth/credentials.d.ts +73 -0
- package/dist/auth/credentials.d.ts.map +1 -0
- package/dist/auth/credentials.js +150 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/auth/device.d.ts +58 -0
- package/dist/auth/device.d.ts.map +1 -0
- package/dist/auth/device.js +235 -0
- package/dist/auth/device.js.map +1 -0
- package/dist/auth/token.d.ts +55 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +153 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/cli.js +51 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/auto.d.ts.map +1 -1
- package/dist/commands/auto.js +321 -236
- package/dist/commands/auto.js.map +1 -1
- package/dist/commands/complete.d.ts +1 -0
- package/dist/commands/complete.d.ts.map +1 -1
- package/dist/commands/complete.js +30 -0
- package/dist/commands/complete.js.map +1 -1
- package/dist/commands/connect.d.ts +25 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +305 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +5 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +119 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +15 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +50 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/next.d.ts +1 -0
- package/dist/commands/next.d.ts.map +1 -1
- package/dist/commands/next.js +41 -2
- package/dist/commands/next.js.map +1 -1
- package/dist/commands/project.d.ts +13 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +92 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/session.d.ts +10 -2
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +80 -3
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +51 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts +33 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +555 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/work.d.ts +1 -0
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +22 -1
- package/dist/commands/work.js.map +1 -1
- package/dist/config/global.d.ts +54 -0
- package/dist/config/global.d.ts.map +1 -0
- package/dist/config/global.js +110 -0
- package/dist/config/global.js.map +1 -0
- package/dist/prompts/coding.d.ts +31 -0
- package/dist/prompts/coding.d.ts.map +1 -0
- package/dist/prompts/coding.js +228 -0
- package/dist/prompts/coding.js.map +1 -0
- package/dist/prompts/index.d.ts +10 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +10 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/initializer.d.ts +20 -0
- package/dist/prompts/initializer.d.ts.map +1 -0
- package/dist/prompts/initializer.js +147 -0
- package/dist/prompts/initializer.js.map +1 -0
- package/dist/sdk/client.d.ts +67 -0
- package/dist/sdk/client.d.ts.map +1 -0
- package/dist/sdk/client.js +196 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/index.d.ts +8 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +8 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sdk/security.d.ts +43 -0
- package/dist/sdk/security.d.ts.map +1 -0
- package/dist/sdk/security.js +214 -0
- package/dist/sdk/security.js.map +1 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/connection.d.ts +126 -0
- package/dist/utils/connection.d.ts.map +1 -0
- package/dist/utils/connection.js +226 -0
- package/dist/utils/connection.js.map +1 -0
- package/dist/utils/initializerAgent.d.ts +0 -5
- package/dist/utils/initializerAgent.d.ts.map +1 -1
- package/dist/utils/initializerAgent.js +112 -31
- package/dist/utils/initializerAgent.js.map +1 -1
- package/dist/utils/messages.d.ts +76 -0
- package/dist/utils/messages.d.ts.map +1 -1
- package/dist/utils/messages.js +87 -1
- package/dist/utils/messages.js.map +1 -1
- package/dist/utils/output.d.ts +10 -0
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +31 -0
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/prompt.d.ts +10 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +22 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/sseClient.d.ts +183 -0
- package/dist/utils/sseClient.d.ts.map +1 -0
- package/dist/utils/sseClient.js +391 -0
- package/dist/utils/sseClient.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials 관리 모듈
|
|
3
|
+
*
|
|
4
|
+
* ~/.aiag/credentials 파일 관리
|
|
5
|
+
*
|
|
6
|
+
* 저장 형식 (JSON):
|
|
7
|
+
* {
|
|
8
|
+
* "serverUrl": "https://aiag-adp.example.com",
|
|
9
|
+
* "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
10
|
+
* "refreshToken": "...",
|
|
11
|
+
* "expiresAt": "2025-01-01T00:00:00Z",
|
|
12
|
+
* "userId": "user_123",
|
|
13
|
+
* "email": "user@example.com"
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
17
|
+
import { join } from 'path';
|
|
18
|
+
import { ensureGlobalConfigDir, getGlobalConfigDir } from '../config/global.js';
|
|
19
|
+
/**
|
|
20
|
+
* Credentials 파일 경로 반환
|
|
21
|
+
* @returns ~/.aiag/credentials
|
|
22
|
+
*/
|
|
23
|
+
export function getCredentialsPath() {
|
|
24
|
+
return join(getGlobalConfigDir(), 'credentials');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 저장된 credentials 로드
|
|
28
|
+
* @returns Credentials 또는 null (없거나 유효하지 않은 경우)
|
|
29
|
+
*/
|
|
30
|
+
export function loadCredentials() {
|
|
31
|
+
const credentialsPath = getCredentialsPath();
|
|
32
|
+
if (!existsSync(credentialsPath)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const content = readFileSync(credentialsPath, 'utf-8');
|
|
37
|
+
const credentials = JSON.parse(content);
|
|
38
|
+
// 필수 필드 검증
|
|
39
|
+
if (!credentials.serverUrl ||
|
|
40
|
+
!credentials.accessToken ||
|
|
41
|
+
!credentials.expiresAt ||
|
|
42
|
+
!credentials.userId ||
|
|
43
|
+
!credentials.email) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return credentials;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Credentials 저장
|
|
54
|
+
* @param credentials 저장할 인증 정보
|
|
55
|
+
*/
|
|
56
|
+
export function saveCredentials(credentials) {
|
|
57
|
+
ensureGlobalConfigDir();
|
|
58
|
+
const credentialsPath = getCredentialsPath();
|
|
59
|
+
writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), {
|
|
60
|
+
mode: 0o600, // 소유자만 읽기/쓰기
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Credentials 삭제
|
|
65
|
+
*/
|
|
66
|
+
export function clearCredentials() {
|
|
67
|
+
const credentialsPath = getCredentialsPath();
|
|
68
|
+
if (existsSync(credentialsPath)) {
|
|
69
|
+
unlinkSync(credentialsPath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 로그인 상태 확인
|
|
74
|
+
* @returns 로그인 되어 있으면 true
|
|
75
|
+
*/
|
|
76
|
+
export function isLoggedIn() {
|
|
77
|
+
const credentials = loadCredentials();
|
|
78
|
+
if (!credentials) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
// 토큰 만료 확인
|
|
82
|
+
return !isTokenExpired(credentials);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 토큰 만료 여부 확인
|
|
86
|
+
* @param credentials 인증 정보
|
|
87
|
+
* @returns 만료되었으면 true
|
|
88
|
+
*/
|
|
89
|
+
export function isTokenExpired(credentials) {
|
|
90
|
+
const expiresAt = new Date(credentials.expiresAt);
|
|
91
|
+
const now = new Date();
|
|
92
|
+
// 5분 여유를 두고 만료 판정 (갱신 시간 확보)
|
|
93
|
+
const bufferMs = 5 * 60 * 1000;
|
|
94
|
+
return now.getTime() >= expiresAt.getTime() - bufferMs;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 토큰 만료까지 남은 시간 (초)
|
|
98
|
+
* @param credentials 인증 정보
|
|
99
|
+
* @returns 남은 시간 (초), 이미 만료되었으면 0
|
|
100
|
+
*/
|
|
101
|
+
export function getTokenTimeRemaining(credentials) {
|
|
102
|
+
const expiresAt = new Date(credentials.expiresAt);
|
|
103
|
+
const now = new Date();
|
|
104
|
+
const remaining = Math.floor((expiresAt.getTime() - now.getTime()) / 1000);
|
|
105
|
+
return Math.max(0, remaining);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 현재 로그인된 사용자 이메일 반환
|
|
109
|
+
* @returns 이메일 또는 null
|
|
110
|
+
*/
|
|
111
|
+
export function getCurrentUserEmail() {
|
|
112
|
+
const credentials = loadCredentials();
|
|
113
|
+
return credentials?.email ?? null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 현재 연결된 서버 URL 반환
|
|
117
|
+
* @returns 서버 URL 또는 null
|
|
118
|
+
*/
|
|
119
|
+
export function getCurrentServerUrl() {
|
|
120
|
+
const credentials = loadCredentials();
|
|
121
|
+
return credentials?.serverUrl ?? null;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Access Token 반환 (유효한 경우만)
|
|
125
|
+
* @returns 액세스 토큰 또는 null (만료 또는 없음)
|
|
126
|
+
*/
|
|
127
|
+
export function getAccessToken() {
|
|
128
|
+
const credentials = loadCredentials();
|
|
129
|
+
if (!credentials || isTokenExpired(credentials)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return credentials.accessToken;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Credentials 업데이트 (부분 업데이트)
|
|
136
|
+
* 기존 credentials가 있는 경우에만 동작
|
|
137
|
+
*/
|
|
138
|
+
export function updateCredentials(updates) {
|
|
139
|
+
const credentials = loadCredentials();
|
|
140
|
+
if (!credentials) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const updated = {
|
|
144
|
+
...credentials,
|
|
145
|
+
...updates,
|
|
146
|
+
};
|
|
147
|
+
saveCredentials(updated);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/auth/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 디바이스 인증 플로우 모듈
|
|
3
|
+
*
|
|
4
|
+
* OAuth 2.0 Device Authorization Grant (RFC 8628) 구현
|
|
5
|
+
*
|
|
6
|
+
* 플로우:
|
|
7
|
+
* 1. POST /api/auth/device/request → device_code, user_code 수신
|
|
8
|
+
* 2. 브라우저 열기 (인증 URL)
|
|
9
|
+
* 3. GET /api/auth/device/poll 폴링
|
|
10
|
+
* 4. 토큰 수신 → credentials 저장
|
|
11
|
+
*/
|
|
12
|
+
import type { DeviceCodeResponse, DeviceTokenResponse, DevicePollStatus, Credentials } from '../types.js';
|
|
13
|
+
/**
|
|
14
|
+
* 디바이스 인증 코드 요청
|
|
15
|
+
*
|
|
16
|
+
* @param serverUrl aiag-adp 서버 URL
|
|
17
|
+
* @returns 디바이스 코드 응답
|
|
18
|
+
* @throws Error 서버 연결 실패 또는 응답 오류 시
|
|
19
|
+
*/
|
|
20
|
+
export declare function requestDeviceCode(serverUrl: string): Promise<DeviceCodeResponse>;
|
|
21
|
+
/**
|
|
22
|
+
* 디바이스 인증 토큰 폴링
|
|
23
|
+
*
|
|
24
|
+
* @param serverUrl aiag-adp 서버 URL
|
|
25
|
+
* @param deviceCode 디바이스 코드
|
|
26
|
+
* @returns 폴링 결과 (상태 또는 토큰)
|
|
27
|
+
*/
|
|
28
|
+
export declare function pollForToken(serverUrl: string, deviceCode: string): Promise<{
|
|
29
|
+
status: DevicePollStatus;
|
|
30
|
+
token?: DeviceTokenResponse;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* 디바이스 인증 전체 플로우 실행
|
|
34
|
+
*
|
|
35
|
+
* @param serverUrl 서버 URL
|
|
36
|
+
* @param callbacks 콜백 함수들
|
|
37
|
+
* @returns 성공 시 Credentials
|
|
38
|
+
*/
|
|
39
|
+
export declare function performDeviceAuth(serverUrl: string, callbacks: {
|
|
40
|
+
onCodeReceived: (code: DeviceCodeResponse) => void;
|
|
41
|
+
onPolling: () => void;
|
|
42
|
+
onSuccess: (email: string) => void;
|
|
43
|
+
onError: (error: string) => void;
|
|
44
|
+
}): Promise<Credentials | null>;
|
|
45
|
+
/**
|
|
46
|
+
* 브라우저 열기
|
|
47
|
+
* ESM 동적 import 사용 (open 패키지)
|
|
48
|
+
*/
|
|
49
|
+
export declare function openBrowser(url: string): Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* 서버 URL 유효성 검증
|
|
52
|
+
*/
|
|
53
|
+
export declare function validateServerUrl(serverUrl: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 서버 연결 테스트
|
|
56
|
+
*/
|
|
57
|
+
export declare function testServerConnection(serverUrl: string): Promise<boolean>;
|
|
58
|
+
//# sourceMappingURL=device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../../src/auth/device.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACZ,MAAM,aAAa,CAAC;AAQrB;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA4BtF;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAuDpE;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE;IACT,cAAc,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnD,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,GACA,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAoE7B;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0B/D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAO5D;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAW9E"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 디바이스 인증 플로우 모듈
|
|
3
|
+
*
|
|
4
|
+
* OAuth 2.0 Device Authorization Grant (RFC 8628) 구현
|
|
5
|
+
*
|
|
6
|
+
* 플로우:
|
|
7
|
+
* 1. POST /api/auth/device/request → device_code, user_code 수신
|
|
8
|
+
* 2. 브라우저 열기 (인증 URL)
|
|
9
|
+
* 3. GET /api/auth/device/poll 폴링
|
|
10
|
+
* 4. 토큰 수신 → credentials 저장
|
|
11
|
+
*/
|
|
12
|
+
import { saveCredentials } from './credentials.js';
|
|
13
|
+
import { setDefaultServer } from '../config/global.js';
|
|
14
|
+
// 디바이스 인증 관련 API 엔드포인트
|
|
15
|
+
const DEVICE_REQUEST_ENDPOINT = '/api/auth/device/request';
|
|
16
|
+
const DEVICE_POLL_ENDPOINT = '/api/auth/device/poll';
|
|
17
|
+
/**
|
|
18
|
+
* 디바이스 인증 코드 요청
|
|
19
|
+
*
|
|
20
|
+
* @param serverUrl aiag-adp 서버 URL
|
|
21
|
+
* @returns 디바이스 코드 응답
|
|
22
|
+
* @throws Error 서버 연결 실패 또는 응답 오류 시
|
|
23
|
+
*/
|
|
24
|
+
export async function requestDeviceCode(serverUrl) {
|
|
25
|
+
const url = new URL(DEVICE_REQUEST_ENDPOINT, serverUrl).toString();
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
clientId: 'aiag-cli',
|
|
33
|
+
scope: 'read write',
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const error = await response.text();
|
|
38
|
+
throw new Error(`디바이스 코드 요청 실패: ${response.status} ${error}`);
|
|
39
|
+
}
|
|
40
|
+
const data = await response.json();
|
|
41
|
+
return {
|
|
42
|
+
deviceCode: (data.device_code || data.deviceCode),
|
|
43
|
+
userCode: (data.user_code || data.userCode),
|
|
44
|
+
verificationUri: (data.verification_uri || data.verificationUri),
|
|
45
|
+
expiresIn: (data.expires_in || data.expiresIn || 300),
|
|
46
|
+
interval: (data.interval || 5),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 디바이스 인증 토큰 폴링
|
|
51
|
+
*
|
|
52
|
+
* @param serverUrl aiag-adp 서버 URL
|
|
53
|
+
* @param deviceCode 디바이스 코드
|
|
54
|
+
* @returns 폴링 결과 (상태 또는 토큰)
|
|
55
|
+
*/
|
|
56
|
+
export async function pollForToken(serverUrl, deviceCode) {
|
|
57
|
+
const url = new URL(DEVICE_POLL_ENDPOINT, serverUrl).toString();
|
|
58
|
+
const response = await fetch(url, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
deviceCode,
|
|
65
|
+
clientId: 'aiag-cli',
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
if (response.status === 400) {
|
|
69
|
+
// 아직 인증 대기 중
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
const error = (data.error || data.status);
|
|
72
|
+
switch (error) {
|
|
73
|
+
case 'authorization_pending':
|
|
74
|
+
case 'pending':
|
|
75
|
+
return { status: 'pending' };
|
|
76
|
+
case 'slow_down':
|
|
77
|
+
// 폴링 간격을 늘려야 함 (호출자가 처리)
|
|
78
|
+
return { status: 'pending' };
|
|
79
|
+
case 'expired_token':
|
|
80
|
+
case 'expired':
|
|
81
|
+
return { status: 'expired' };
|
|
82
|
+
case 'access_denied':
|
|
83
|
+
case 'denied':
|
|
84
|
+
return { status: 'denied' };
|
|
85
|
+
default:
|
|
86
|
+
return { status: 'pending' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const error = await response.text();
|
|
91
|
+
throw new Error(`토큰 폴링 실패: ${response.status} ${error}`);
|
|
92
|
+
}
|
|
93
|
+
// 인증 성공
|
|
94
|
+
const data = await response.json();
|
|
95
|
+
return {
|
|
96
|
+
status: 'authorized',
|
|
97
|
+
token: {
|
|
98
|
+
accessToken: (data.access_token || data.accessToken),
|
|
99
|
+
refreshToken: (data.refresh_token || data.refreshToken),
|
|
100
|
+
expiresIn: (data.expires_in || data.expiresIn || 3600),
|
|
101
|
+
userId: (data.user_id || data.userId),
|
|
102
|
+
email: data.email,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 디바이스 인증 전체 플로우 실행
|
|
108
|
+
*
|
|
109
|
+
* @param serverUrl 서버 URL
|
|
110
|
+
* @param callbacks 콜백 함수들
|
|
111
|
+
* @returns 성공 시 Credentials
|
|
112
|
+
*/
|
|
113
|
+
export async function performDeviceAuth(serverUrl, callbacks) {
|
|
114
|
+
try {
|
|
115
|
+
// 1. 디바이스 코드 요청
|
|
116
|
+
const codeResponse = await requestDeviceCode(serverUrl);
|
|
117
|
+
callbacks.onCodeReceived(codeResponse);
|
|
118
|
+
// 2. 폴링 시작
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
const expiresAtMs = startTime + codeResponse.expiresIn * 1000;
|
|
121
|
+
let interval = codeResponse.interval * 1000;
|
|
122
|
+
while (Date.now() < expiresAtMs) {
|
|
123
|
+
callbacks.onPolling();
|
|
124
|
+
// 폴링 간격 대기
|
|
125
|
+
await sleep(interval);
|
|
126
|
+
// 폴링 요청
|
|
127
|
+
const result = await pollForToken(serverUrl, codeResponse.deviceCode);
|
|
128
|
+
switch (result.status) {
|
|
129
|
+
case 'authorized':
|
|
130
|
+
if (result.token) {
|
|
131
|
+
// 토큰 수신 성공
|
|
132
|
+
const expiresAt = new Date(Date.now() + result.token.expiresIn * 1000).toISOString();
|
|
133
|
+
const credentials = {
|
|
134
|
+
serverUrl,
|
|
135
|
+
accessToken: result.token.accessToken,
|
|
136
|
+
refreshToken: result.token.refreshToken,
|
|
137
|
+
expiresAt,
|
|
138
|
+
userId: result.token.userId,
|
|
139
|
+
email: result.token.email,
|
|
140
|
+
};
|
|
141
|
+
// Credentials 저장
|
|
142
|
+
saveCredentials(credentials);
|
|
143
|
+
// 기본 서버로 설정
|
|
144
|
+
setDefaultServer(serverUrl);
|
|
145
|
+
callbacks.onSuccess(result.token.email);
|
|
146
|
+
return credentials;
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
case 'expired':
|
|
150
|
+
callbacks.onError('인증 코드가 만료되었습니다.');
|
|
151
|
+
return null;
|
|
152
|
+
case 'denied':
|
|
153
|
+
callbacks.onError('인증이 거부되었습니다.');
|
|
154
|
+
return null;
|
|
155
|
+
case 'pending':
|
|
156
|
+
// 계속 폴링
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// 타임아웃
|
|
161
|
+
callbacks.onError('인증 시간이 초과되었습니다.');
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류';
|
|
166
|
+
callbacks.onError(message);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 브라우저 열기
|
|
172
|
+
* ESM 동적 import 사용 (open 패키지)
|
|
173
|
+
*/
|
|
174
|
+
export async function openBrowser(url) {
|
|
175
|
+
try {
|
|
176
|
+
// Node.js 18+에서 사용 가능한 방법
|
|
177
|
+
const { exec } = await import('child_process');
|
|
178
|
+
const { platform } = await import('os');
|
|
179
|
+
const platformName = platform();
|
|
180
|
+
let command;
|
|
181
|
+
if (platformName === 'darwin') {
|
|
182
|
+
command = `open "${url}"`;
|
|
183
|
+
}
|
|
184
|
+
else if (platformName === 'win32') {
|
|
185
|
+
command = `start "" "${url}"`;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Linux 및 기타
|
|
189
|
+
command = `xdg-open "${url}"`;
|
|
190
|
+
}
|
|
191
|
+
return new Promise((resolve) => {
|
|
192
|
+
exec(command, (error) => {
|
|
193
|
+
resolve(!error);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 서버 URL 유효성 검증
|
|
203
|
+
*/
|
|
204
|
+
export function validateServerUrl(serverUrl) {
|
|
205
|
+
try {
|
|
206
|
+
const url = new URL(serverUrl);
|
|
207
|
+
return url.protocol === 'https:' || url.protocol === 'http:';
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 서버 연결 테스트
|
|
215
|
+
*/
|
|
216
|
+
export async function testServerConnection(serverUrl) {
|
|
217
|
+
try {
|
|
218
|
+
const url = new URL('/api/health', serverUrl).toString();
|
|
219
|
+
const response = await fetch(url, {
|
|
220
|
+
method: 'GET',
|
|
221
|
+
signal: AbortSignal.timeout(5000), // 5초 타임아웃
|
|
222
|
+
});
|
|
223
|
+
return response.ok;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 유틸리티: sleep
|
|
231
|
+
*/
|
|
232
|
+
function sleep(ms) {
|
|
233
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../../src/auth/device.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 토큰 관리 모듈
|
|
3
|
+
*
|
|
4
|
+
* - 토큰 갱신
|
|
5
|
+
* - 토큰 검증
|
|
6
|
+
* - 토큰 만료 처리
|
|
7
|
+
*/
|
|
8
|
+
import type { Credentials } from '../types.js';
|
|
9
|
+
/**
|
|
10
|
+
* 토큰 갱신 시도
|
|
11
|
+
*
|
|
12
|
+
* @param credentials 현재 인증 정보
|
|
13
|
+
* @returns 갱신된 credentials 또는 null (갱신 실패)
|
|
14
|
+
*/
|
|
15
|
+
export declare function refreshToken(credentials: Credentials): Promise<Credentials | null>;
|
|
16
|
+
/**
|
|
17
|
+
* 토큰 무효화 (로그아웃 시 서버에 알림)
|
|
18
|
+
*
|
|
19
|
+
* @param credentials 인증 정보
|
|
20
|
+
* @returns 성공 여부
|
|
21
|
+
*/
|
|
22
|
+
export declare function revokeToken(credentials: Credentials): Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* 유효한 액세스 토큰 얻기
|
|
25
|
+
* 만료된 경우 자동 갱신 시도
|
|
26
|
+
*
|
|
27
|
+
* @returns 유효한 액세스 토큰 또는 null
|
|
28
|
+
*/
|
|
29
|
+
export declare function getValidAccessToken(): Promise<string | null>;
|
|
30
|
+
/**
|
|
31
|
+
* 토큰 상태 정보
|
|
32
|
+
*/
|
|
33
|
+
export interface TokenStatus {
|
|
34
|
+
isValid: boolean;
|
|
35
|
+
isExpired: boolean;
|
|
36
|
+
hasRefreshToken: boolean;
|
|
37
|
+
timeRemaining: number;
|
|
38
|
+
email: string | null;
|
|
39
|
+
serverUrl: string | null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 토큰 상태 확인
|
|
43
|
+
*/
|
|
44
|
+
export declare function getTokenStatus(): TokenStatus;
|
|
45
|
+
/**
|
|
46
|
+
* 토큰 자동 갱신 여부 확인
|
|
47
|
+
* 만료 10분 전부터 갱신 권장
|
|
48
|
+
*/
|
|
49
|
+
export declare function shouldRefreshToken(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* 토큰이 곧 만료되는지 확인 (경고용)
|
|
52
|
+
* 만료 30분 전
|
|
53
|
+
*/
|
|
54
|
+
export declare function isTokenExpiringSoon(): boolean;
|
|
55
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/auth/token.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAY/C;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAyCxF;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAmB5E;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmBlE;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAwB5C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAU5C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAU7C"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 토큰 관리 모듈
|
|
3
|
+
*
|
|
4
|
+
* - 토큰 갱신
|
|
5
|
+
* - 토큰 검증
|
|
6
|
+
* - 토큰 만료 처리
|
|
7
|
+
*/
|
|
8
|
+
import { loadCredentials, saveCredentials, isTokenExpired, getTokenTimeRemaining, } from './credentials.js';
|
|
9
|
+
// 토큰 갱신 엔드포인트
|
|
10
|
+
const TOKEN_REFRESH_ENDPOINT = '/api/auth/token/refresh';
|
|
11
|
+
const TOKEN_REVOKE_ENDPOINT = '/api/auth/token/revoke';
|
|
12
|
+
/**
|
|
13
|
+
* 토큰 갱신 시도
|
|
14
|
+
*
|
|
15
|
+
* @param credentials 현재 인증 정보
|
|
16
|
+
* @returns 갱신된 credentials 또는 null (갱신 실패)
|
|
17
|
+
*/
|
|
18
|
+
export async function refreshToken(credentials) {
|
|
19
|
+
// refreshToken이 없으면 갱신 불가
|
|
20
|
+
if (!credentials.refreshToken) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(TOKEN_REFRESH_ENDPOINT, credentials.serverUrl).toString();
|
|
25
|
+
const response = await fetch(url, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
refreshToken: credentials.refreshToken,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
// 새 토큰으로 credentials 업데이트
|
|
39
|
+
const expiresIn = (data.expires_in || data.expiresIn || 3600);
|
|
40
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();
|
|
41
|
+
const updated = {
|
|
42
|
+
...credentials,
|
|
43
|
+
accessToken: (data.access_token || data.accessToken),
|
|
44
|
+
refreshToken: (data.refresh_token || data.refreshToken || credentials.refreshToken),
|
|
45
|
+
expiresAt,
|
|
46
|
+
};
|
|
47
|
+
saveCredentials(updated);
|
|
48
|
+
return updated;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 토큰 무효화 (로그아웃 시 서버에 알림)
|
|
56
|
+
*
|
|
57
|
+
* @param credentials 인증 정보
|
|
58
|
+
* @returns 성공 여부
|
|
59
|
+
*/
|
|
60
|
+
export async function revokeToken(credentials) {
|
|
61
|
+
try {
|
|
62
|
+
const url = new URL(TOKEN_REVOKE_ENDPOINT, credentials.serverUrl).toString();
|
|
63
|
+
const response = await fetch(url, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
token: credentials.accessToken,
|
|
71
|
+
}),
|
|
72
|
+
});
|
|
73
|
+
return response.ok;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 유효한 액세스 토큰 얻기
|
|
81
|
+
* 만료된 경우 자동 갱신 시도
|
|
82
|
+
*
|
|
83
|
+
* @returns 유효한 액세스 토큰 또는 null
|
|
84
|
+
*/
|
|
85
|
+
export async function getValidAccessToken() {
|
|
86
|
+
const credentials = loadCredentials();
|
|
87
|
+
if (!credentials) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
// 토큰이 유효한 경우
|
|
91
|
+
if (!isTokenExpired(credentials)) {
|
|
92
|
+
return credentials.accessToken;
|
|
93
|
+
}
|
|
94
|
+
// 토큰 갱신 시도
|
|
95
|
+
const refreshed = await refreshToken(credentials);
|
|
96
|
+
if (refreshed) {
|
|
97
|
+
return refreshed.accessToken;
|
|
98
|
+
}
|
|
99
|
+
// 갱신 실패
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 토큰 상태 확인
|
|
104
|
+
*/
|
|
105
|
+
export function getTokenStatus() {
|
|
106
|
+
const credentials = loadCredentials();
|
|
107
|
+
if (!credentials) {
|
|
108
|
+
return {
|
|
109
|
+
isValid: false,
|
|
110
|
+
isExpired: true,
|
|
111
|
+
hasRefreshToken: false,
|
|
112
|
+
timeRemaining: 0,
|
|
113
|
+
email: null,
|
|
114
|
+
serverUrl: null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const expired = isTokenExpired(credentials);
|
|
118
|
+
return {
|
|
119
|
+
isValid: !expired,
|
|
120
|
+
isExpired: expired,
|
|
121
|
+
hasRefreshToken: !!credentials.refreshToken,
|
|
122
|
+
timeRemaining: getTokenTimeRemaining(credentials),
|
|
123
|
+
email: credentials.email,
|
|
124
|
+
serverUrl: credentials.serverUrl,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 토큰 자동 갱신 여부 확인
|
|
129
|
+
* 만료 10분 전부터 갱신 권장
|
|
130
|
+
*/
|
|
131
|
+
export function shouldRefreshToken() {
|
|
132
|
+
const credentials = loadCredentials();
|
|
133
|
+
if (!credentials || !credentials.refreshToken) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const remaining = getTokenTimeRemaining(credentials);
|
|
137
|
+
const thresholdSeconds = 10 * 60; // 10분
|
|
138
|
+
return remaining > 0 && remaining < thresholdSeconds;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 토큰이 곧 만료되는지 확인 (경고용)
|
|
142
|
+
* 만료 30분 전
|
|
143
|
+
*/
|
|
144
|
+
export function isTokenExpiringSoon() {
|
|
145
|
+
const credentials = loadCredentials();
|
|
146
|
+
if (!credentials) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const remaining = getTokenTimeRemaining(credentials);
|
|
150
|
+
const thresholdSeconds = 30 * 60; // 30분
|
|
151
|
+
return remaining > 0 && remaining < thresholdSeconds;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/auth/token.ts"],"names":[],"mappings":"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"}
|