@shin1ohno/sage 0.11.0 → 0.12.1
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 +39 -0
- package/dist/cli/http-server-with-config.d.ts.map +1 -1
- package/dist/cli/http-server-with-config.js +45 -1
- package/dist/cli/http-server-with-config.js.map +1 -1
- package/dist/cli/mcp-handler.d.ts.map +1 -1
- package/dist/cli/mcp-handler.js +59 -0
- package/dist/cli/mcp-handler.js.map +1 -1
- package/dist/oauth/google-oauth-callback-handler.d.ts +57 -0
- package/dist/oauth/google-oauth-callback-handler.d.ts.map +1 -0
- package/dist/oauth/google-oauth-callback-handler.js +291 -0
- package/dist/oauth/google-oauth-callback-handler.js.map +1 -0
- package/dist/oauth/index.d.ts +2 -0
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js +4 -0
- package/dist/oauth/index.js.map +1 -1
- package/dist/oauth/pending-google-auth-store.d.ts +88 -0
- package/dist/oauth/pending-google-auth-store.d.ts.map +1 -0
- package/dist/oauth/pending-google-auth-store.js +218 -0
- package/dist/oauth/pending-google-auth-store.js.map +1 -0
- package/dist/tools/oauth/authenticate-google.d.ts +18 -7
- package/dist/tools/oauth/authenticate-google.d.ts.map +1 -1
- package/dist/tools/oauth/authenticate-google.js +95 -9
- package/dist/tools/oauth/authenticate-google.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google OAuth Callback Handler
|
|
3
|
+
* Requirements: FR-1 (OAuth Callback Endpoint), FR-4 (Token Exchange)
|
|
4
|
+
*
|
|
5
|
+
* Handles HTTP callbacks from Google OAuth for remote server mode.
|
|
6
|
+
* Processes authorization codes, exchanges for tokens, and renders
|
|
7
|
+
* success/error pages to the browser.
|
|
8
|
+
*/
|
|
9
|
+
import { URL } from 'url';
|
|
10
|
+
import { oauthLogger } from '../utils/logger.js';
|
|
11
|
+
/**
|
|
12
|
+
* Google OAuth Callback Handler
|
|
13
|
+
*
|
|
14
|
+
* Processes OAuth callbacks from Google and exchanges
|
|
15
|
+
* authorization codes for tokens.
|
|
16
|
+
*/
|
|
17
|
+
export class GoogleOAuthCallbackHandler {
|
|
18
|
+
pendingAuthStore;
|
|
19
|
+
googleOAuthHandler;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.pendingAuthStore = options.pendingAuthStore;
|
|
22
|
+
this.googleOAuthHandler = options.googleOAuthHandler;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Handle OAuth callback request
|
|
26
|
+
*
|
|
27
|
+
* @param req - HTTP request
|
|
28
|
+
* @param res - HTTP response
|
|
29
|
+
*/
|
|
30
|
+
async handleCallback(req, res) {
|
|
31
|
+
try {
|
|
32
|
+
const params = this.parseCallbackParams(req.url || '');
|
|
33
|
+
oauthLogger.info({ state: params.state, hasCode: !!params.code, error: params.error }, 'Processing OAuth callback');
|
|
34
|
+
// Check for OAuth error
|
|
35
|
+
if (params.error) {
|
|
36
|
+
const errorMessage = params.error === 'access_denied'
|
|
37
|
+
? '認証が拒否されました。'
|
|
38
|
+
: `OAuth エラー: ${params.error}`;
|
|
39
|
+
this.renderErrorPage(res, errorMessage, params.errorDescription);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Validate required parameters
|
|
43
|
+
if (!params.state) {
|
|
44
|
+
this.renderErrorPage(res, '認証セッションが見つかりません。', 'state パラメータがありません。');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!params.code) {
|
|
48
|
+
this.renderErrorPage(res, '認証コードが見つかりません。', 'code パラメータがありません。');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Find pending session
|
|
52
|
+
const session = this.pendingAuthStore.findByState(params.state);
|
|
53
|
+
if (!session) {
|
|
54
|
+
this.renderErrorPage(res, '認証セッションが見つかりません。', 'セッションが期限切れか、無効な state です。再度認証を開始してください。');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Exchange code for tokens
|
|
58
|
+
await this.exchangeCodeForTokens(params.code, session);
|
|
59
|
+
// Remove session after successful exchange
|
|
60
|
+
this.pendingAuthStore.remove(params.state);
|
|
61
|
+
// Render success page
|
|
62
|
+
this.renderSuccessPage(res);
|
|
63
|
+
oauthLogger.info({ state: params.state }, 'OAuth callback completed successfully');
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
oauthLogger.error({ err: error }, 'OAuth callback failed');
|
|
67
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
68
|
+
this.renderErrorPage(res, '認証に失敗しました。', errorMessage);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Parse callback parameters from URL
|
|
73
|
+
*/
|
|
74
|
+
parseCallbackParams(urlString) {
|
|
75
|
+
try {
|
|
76
|
+
// Handle relative URLs by adding a base
|
|
77
|
+
const url = new URL(urlString, 'http://localhost');
|
|
78
|
+
const searchParams = url.searchParams;
|
|
79
|
+
return {
|
|
80
|
+
code: searchParams.get('code') || undefined,
|
|
81
|
+
state: searchParams.get('state') || undefined,
|
|
82
|
+
error: searchParams.get('error') || undefined,
|
|
83
|
+
errorDescription: searchParams.get('error_description') || undefined,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
oauthLogger.error({ err: error, url: urlString }, 'Failed to parse callback URL');
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Exchange authorization code for tokens
|
|
93
|
+
*/
|
|
94
|
+
async exchangeCodeForTokens(code, session) {
|
|
95
|
+
// Use googleapis directly for token exchange since we have the code_verifier
|
|
96
|
+
// stored in the session (GoogleOAuthHandler requires calling getAuthorizationUrl()
|
|
97
|
+
// first to set its internal codeVerifier, which doesn't work for remote mode)
|
|
98
|
+
const { google } = await import('googleapis');
|
|
99
|
+
const oauth2Client = new google.auth.OAuth2(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, session.redirectUri);
|
|
100
|
+
const { tokens } = await oauth2Client.getToken({
|
|
101
|
+
code,
|
|
102
|
+
codeVerifier: session.codeVerifier,
|
|
103
|
+
});
|
|
104
|
+
if (!tokens.access_token || !tokens.refresh_token) {
|
|
105
|
+
throw new Error('トークンの取得に失敗しました。access_token または refresh_token がありません。');
|
|
106
|
+
}
|
|
107
|
+
// Store tokens using the GoogleOAuthHandler
|
|
108
|
+
await this.googleOAuthHandler.storeTokens({
|
|
109
|
+
accessToken: tokens.access_token,
|
|
110
|
+
refreshToken: tokens.refresh_token,
|
|
111
|
+
expiresAt: tokens.expiry_date || Date.now() + 3600 * 1000,
|
|
112
|
+
scope: tokens.scope ? tokens.scope.split(' ') : ['https://www.googleapis.com/auth/calendar'],
|
|
113
|
+
});
|
|
114
|
+
oauthLogger.info('Tokens exchanged and stored successfully');
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Render success HTML page
|
|
118
|
+
*/
|
|
119
|
+
renderSuccessPage(res) {
|
|
120
|
+
const html = `<!DOCTYPE html>
|
|
121
|
+
<html lang="ja">
|
|
122
|
+
<head>
|
|
123
|
+
<title>sage - Google Calendar 認証完了</title>
|
|
124
|
+
<meta charset="utf-8">
|
|
125
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
126
|
+
<style>
|
|
127
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
128
|
+
body {
|
|
129
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
130
|
+
text-align: center;
|
|
131
|
+
padding: 50px 20px;
|
|
132
|
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
133
|
+
min-height: 100vh;
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: center;
|
|
137
|
+
}
|
|
138
|
+
.container {
|
|
139
|
+
max-width: 400px;
|
|
140
|
+
background: white;
|
|
141
|
+
padding: 40px;
|
|
142
|
+
border-radius: 16px;
|
|
143
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
|
144
|
+
}
|
|
145
|
+
.success-icon {
|
|
146
|
+
width: 80px;
|
|
147
|
+
height: 80px;
|
|
148
|
+
background: #22c55e;
|
|
149
|
+
border-radius: 50%;
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
margin: 0 auto 24px;
|
|
154
|
+
}
|
|
155
|
+
.success-icon svg {
|
|
156
|
+
width: 40px;
|
|
157
|
+
height: 40px;
|
|
158
|
+
stroke: white;
|
|
159
|
+
stroke-width: 3;
|
|
160
|
+
fill: none;
|
|
161
|
+
}
|
|
162
|
+
h1 {
|
|
163
|
+
color: #111827;
|
|
164
|
+
font-size: 24px;
|
|
165
|
+
margin-bottom: 12px;
|
|
166
|
+
}
|
|
167
|
+
p {
|
|
168
|
+
color: #6b7280;
|
|
169
|
+
font-size: 16px;
|
|
170
|
+
line-height: 1.5;
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
173
|
+
</head>
|
|
174
|
+
<body>
|
|
175
|
+
<div class="container">
|
|
176
|
+
<div class="success-icon">
|
|
177
|
+
<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
|
178
|
+
</div>
|
|
179
|
+
<h1>認証が完了しました</h1>
|
|
180
|
+
<p>このウィンドウを閉じて、Claude に戻ってください。</p>
|
|
181
|
+
</div>
|
|
182
|
+
</body>
|
|
183
|
+
</html>`;
|
|
184
|
+
res.writeHead(200, {
|
|
185
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
186
|
+
'Cache-Control': 'no-store',
|
|
187
|
+
});
|
|
188
|
+
res.end(html);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Render error HTML page
|
|
192
|
+
*/
|
|
193
|
+
renderErrorPage(res, error, detail) {
|
|
194
|
+
const detailHtml = detail
|
|
195
|
+
? `<div class="error-detail">${this.escapeHtml(detail)}</div>`
|
|
196
|
+
: '';
|
|
197
|
+
const html = `<!DOCTYPE html>
|
|
198
|
+
<html lang="ja">
|
|
199
|
+
<head>
|
|
200
|
+
<title>sage - Google Calendar 認証エラー</title>
|
|
201
|
+
<meta charset="utf-8">
|
|
202
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
203
|
+
<style>
|
|
204
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
205
|
+
body {
|
|
206
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
207
|
+
text-align: center;
|
|
208
|
+
padding: 50px 20px;
|
|
209
|
+
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
|
|
210
|
+
min-height: 100vh;
|
|
211
|
+
display: flex;
|
|
212
|
+
align-items: center;
|
|
213
|
+
justify-content: center;
|
|
214
|
+
}
|
|
215
|
+
.container {
|
|
216
|
+
max-width: 400px;
|
|
217
|
+
background: white;
|
|
218
|
+
padding: 40px;
|
|
219
|
+
border-radius: 16px;
|
|
220
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
|
221
|
+
}
|
|
222
|
+
.error-icon {
|
|
223
|
+
width: 80px;
|
|
224
|
+
height: 80px;
|
|
225
|
+
background: #ef4444;
|
|
226
|
+
border-radius: 50%;
|
|
227
|
+
display: flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
justify-content: center;
|
|
230
|
+
margin: 0 auto 24px;
|
|
231
|
+
}
|
|
232
|
+
.error-icon svg {
|
|
233
|
+
width: 40px;
|
|
234
|
+
height: 40px;
|
|
235
|
+
stroke: white;
|
|
236
|
+
stroke-width: 3;
|
|
237
|
+
fill: none;
|
|
238
|
+
}
|
|
239
|
+
h1 {
|
|
240
|
+
color: #111827;
|
|
241
|
+
font-size: 24px;
|
|
242
|
+
margin-bottom: 12px;
|
|
243
|
+
}
|
|
244
|
+
p {
|
|
245
|
+
color: #6b7280;
|
|
246
|
+
font-size: 16px;
|
|
247
|
+
line-height: 1.5;
|
|
248
|
+
margin-bottom: 16px;
|
|
249
|
+
}
|
|
250
|
+
.error-detail {
|
|
251
|
+
background: #fef2f2;
|
|
252
|
+
color: #991b1b;
|
|
253
|
+
padding: 12px 16px;
|
|
254
|
+
border-radius: 8px;
|
|
255
|
+
font-size: 14px;
|
|
256
|
+
text-align: left;
|
|
257
|
+
word-break: break-word;
|
|
258
|
+
}
|
|
259
|
+
</style>
|
|
260
|
+
</head>
|
|
261
|
+
<body>
|
|
262
|
+
<div class="container">
|
|
263
|
+
<div class="error-icon">
|
|
264
|
+
<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
265
|
+
</div>
|
|
266
|
+
<h1>認証に失敗しました</h1>
|
|
267
|
+
<p>${this.escapeHtml(error)}</p>
|
|
268
|
+
${detailHtml}
|
|
269
|
+
<p style="margin-top: 16px;">Claude に戻って再試行してください。</p>
|
|
270
|
+
</div>
|
|
271
|
+
</body>
|
|
272
|
+
</html>`;
|
|
273
|
+
res.writeHead(200, {
|
|
274
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
275
|
+
'Cache-Control': 'no-store',
|
|
276
|
+
});
|
|
277
|
+
res.end(html);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Escape HTML special characters
|
|
281
|
+
*/
|
|
282
|
+
escapeHtml(text) {
|
|
283
|
+
return text
|
|
284
|
+
.replace(/&/g, '&')
|
|
285
|
+
.replace(/</g, '<')
|
|
286
|
+
.replace(/>/g, '>')
|
|
287
|
+
.replace(/"/g, '"')
|
|
288
|
+
.replace(/'/g, ''');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=google-oauth-callback-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-oauth-callback-handler.js","sourceRoot":"","sources":["../../src/oauth/google-oauth-callback-handler.ts"],"names":[],"mappings":"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"}
|
package/dist/oauth/index.d.ts
CHANGED
|
@@ -13,4 +13,6 @@ export { OAuthHandler, createOAuthHandler, OAuthHandlerConfig } from './oauth-ha
|
|
|
13
13
|
export { FileMutex, FileMutexMetrics } from './file-mutex.js';
|
|
14
14
|
export { OAuthCallbackServer, OAuthCallbackServerOptions, CallbackResult, } from './oauth-callback-server.js';
|
|
15
15
|
export { GoogleOAuthHandler, GoogleOAuthConfig, GoogleOAuthTokens } from './google-oauth-handler.js';
|
|
16
|
+
export { PendingGoogleAuthStore, PendingGoogleAuth, CreatePendingAuthResult, } from './pending-google-auth-store.js';
|
|
17
|
+
export { GoogleOAuthCallbackHandler, GoogleOAuthCallbackHandlerOptions, } from './google-oauth-callback-handler.js';
|
|
16
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,YAAY,CAAC;AAG3B,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGxI,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvF,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGvF,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGtF,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGnE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtF,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAG1F,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,cAAc,GACf,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,YAAY,CAAC;AAG3B,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGxI,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvF,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGvF,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGtF,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGnE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtF,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAG1F,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,cAAc,GACf,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAGrG,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,0BAA0B,EAC1B,iCAAiC,GAClC,MAAM,oCAAoC,CAAC"}
|
package/dist/oauth/index.js
CHANGED
|
@@ -24,4 +24,8 @@ export { FileMutex } from './file-mutex.js';
|
|
|
24
24
|
export { OAuthCallbackServer, } from './oauth-callback-server.js';
|
|
25
25
|
// Google OAuth Handler
|
|
26
26
|
export { GoogleOAuthHandler } from './google-oauth-handler.js';
|
|
27
|
+
// Pending Google Auth Store (for remote OAuth mode)
|
|
28
|
+
export { PendingGoogleAuthStore, } from './pending-google-auth-store.js';
|
|
29
|
+
// Google OAuth Callback Handler (for remote OAuth mode)
|
|
30
|
+
export { GoogleOAuthCallbackHandler, } from './google-oauth-callback-handler.js';
|
|
27
31
|
//# sourceMappingURL=index.js.map
|
package/dist/oauth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,QAAQ;AACR,cAAc,YAAY,CAAC;AAE3B,OAAO;AACP,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAExI,gBAAgB;AAChB,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAgB,MAAM,oBAAoB,CAAC;AAEvF,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAA0B,MAAM,iBAAiB,CAAC;AAEvF,sBAAsB;AACtB,OAAO,EAAE,uBAAuB,EAAqB,MAAM,0BAA0B,CAAC;AAEtF,eAAe;AACf,OAAO,EAAE,iBAAiB,EAAe,MAAM,mBAAmB,CAAC;AAEnE,eAAe;AACf,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAqB,MAAM,mBAAmB,CAAC;AAEtF,gBAAgB;AAChB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAsB,MAAM,oBAAoB,CAAC;AAE1F,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAoB,MAAM,iBAAiB,CAAC;AAE9D,gDAAgD;AAChD,OAAO,EACL,mBAAmB,GAGpB,MAAM,4BAA4B,CAAC;AAEpC,uBAAuB;AACvB,OAAO,EAAE,kBAAkB,EAAwC,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,QAAQ;AACR,cAAc,YAAY,CAAC;AAE3B,OAAO;AACP,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAExI,gBAAgB;AAChB,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAgB,MAAM,oBAAoB,CAAC;AAEvF,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAA0B,MAAM,iBAAiB,CAAC;AAEvF,sBAAsB;AACtB,OAAO,EAAE,uBAAuB,EAAqB,MAAM,0BAA0B,CAAC;AAEtF,eAAe;AACf,OAAO,EAAE,iBAAiB,EAAe,MAAM,mBAAmB,CAAC;AAEnE,eAAe;AACf,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAqB,MAAM,mBAAmB,CAAC;AAEtF,gBAAgB;AAChB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAsB,MAAM,oBAAoB,CAAC;AAE1F,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAoB,MAAM,iBAAiB,CAAC;AAE9D,gDAAgD;AAChD,OAAO,EACL,mBAAmB,GAGpB,MAAM,4BAA4B,CAAC;AAEpC,uBAAuB;AACvB,OAAO,EAAE,kBAAkB,EAAwC,MAAM,2BAA2B,CAAC;AAErG,oDAAoD;AACpD,OAAO,EACL,sBAAsB,GAGvB,MAAM,gCAAgC,CAAC;AAExC,wDAAwD;AACxD,OAAO,EACL,0BAA0B,GAE3B,MAAM,oCAAoC,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending Google Auth Store
|
|
3
|
+
* Requirements: FR-3 (Pending Auth Session Management)
|
|
4
|
+
*
|
|
5
|
+
* Manages pending Google OAuth authentication sessions for remote mode.
|
|
6
|
+
* Sessions are stored encrypted and expire after a configurable timeout.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Pending Google Auth Session
|
|
10
|
+
*/
|
|
11
|
+
export interface PendingGoogleAuth {
|
|
12
|
+
state: string;
|
|
13
|
+
codeVerifier: string;
|
|
14
|
+
redirectUri: string;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
expiresAt: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Result of creating a new pending auth session
|
|
20
|
+
*/
|
|
21
|
+
export interface CreatePendingAuthResult {
|
|
22
|
+
state: string;
|
|
23
|
+
codeVerifier: string;
|
|
24
|
+
codeChallenge: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pending Google Auth Store
|
|
28
|
+
*
|
|
29
|
+
* Manages pending OAuth sessions with encrypted persistence.
|
|
30
|
+
*/
|
|
31
|
+
export declare class PendingGoogleAuthStore {
|
|
32
|
+
private sessions;
|
|
33
|
+
private readonly storagePath;
|
|
34
|
+
private readonly encryptionService;
|
|
35
|
+
private readonly sessionTimeoutMs;
|
|
36
|
+
private cleanupTimer;
|
|
37
|
+
private initialized;
|
|
38
|
+
constructor(encryptionKey?: string);
|
|
39
|
+
/**
|
|
40
|
+
* Initialize the store and start cleanup timer
|
|
41
|
+
*/
|
|
42
|
+
initialize(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Create a new pending auth session
|
|
45
|
+
*
|
|
46
|
+
* @param redirectUri - The OAuth callback URL
|
|
47
|
+
* @returns Session state, code_verifier, and code_challenge
|
|
48
|
+
*/
|
|
49
|
+
create(redirectUri: string): CreatePendingAuthResult;
|
|
50
|
+
/**
|
|
51
|
+
* Find a session by state
|
|
52
|
+
*
|
|
53
|
+
* @param state - The session state (UUID)
|
|
54
|
+
* @returns The session or null if not found/expired
|
|
55
|
+
*/
|
|
56
|
+
findByState(state: string): PendingGoogleAuth | null;
|
|
57
|
+
/**
|
|
58
|
+
* Remove a session by state
|
|
59
|
+
*
|
|
60
|
+
* @param state - The session state to remove
|
|
61
|
+
*/
|
|
62
|
+
remove(state: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Clean up expired sessions
|
|
65
|
+
*/
|
|
66
|
+
cleanupExpired(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Get session timeout in seconds
|
|
69
|
+
*/
|
|
70
|
+
getSessionTimeoutSeconds(): number;
|
|
71
|
+
/**
|
|
72
|
+
* Persist sessions to encrypted file
|
|
73
|
+
*/
|
|
74
|
+
persist(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Load sessions from encrypted file
|
|
77
|
+
*/
|
|
78
|
+
load(): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Shutdown the store and stop cleanup timer
|
|
81
|
+
*/
|
|
82
|
+
shutdown(): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Get the number of active sessions
|
|
85
|
+
*/
|
|
86
|
+
getSessionCount(): number;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=pending-google-auth-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-google-auth-store.d.ts","sourceRoot":"","sources":["../../src/oauth/pending-google-auth-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAUD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAYD;;;;GAIG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAA6C;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,WAAW,CAAkB;gBAEzB,aAAa,CAAC,EAAE,MAAM;IAalC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBjC;;;;;OAKG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,uBAAuB;IA8BpD;;;;;OAKG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAiBpD;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAa3B;;OAEG;IACH,cAAc,IAAI,IAAI;IAqBtB;;OAEG;IACH,wBAAwB,IAAI,MAAM;IAIlC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB9B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B3B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAY/B;;OAEG;IACH,eAAe,IAAI,MAAM;CAG1B"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending Google Auth Store
|
|
3
|
+
* Requirements: FR-3 (Pending Auth Session Management)
|
|
4
|
+
*
|
|
5
|
+
* Manages pending Google OAuth authentication sessions for remote mode.
|
|
6
|
+
* Sessions are stored encrypted and expire after a configurable timeout.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from 'crypto';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { generateCodeVerifier, generateCodeChallenge } from './pkce.js';
|
|
12
|
+
import { EncryptionService } from './encryption-service.js';
|
|
13
|
+
import { oauthLogger } from '../utils/logger.js';
|
|
14
|
+
/**
|
|
15
|
+
* Default session timeout (10 minutes)
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_SESSION_TIMEOUT_MS = 10 * 60 * 1000;
|
|
18
|
+
/**
|
|
19
|
+
* Cleanup interval (5 minutes)
|
|
20
|
+
*/
|
|
21
|
+
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Pending Google Auth Store
|
|
24
|
+
*
|
|
25
|
+
* Manages pending OAuth sessions with encrypted persistence.
|
|
26
|
+
*/
|
|
27
|
+
export class PendingGoogleAuthStore {
|
|
28
|
+
sessions = new Map();
|
|
29
|
+
storagePath;
|
|
30
|
+
encryptionService;
|
|
31
|
+
sessionTimeoutMs;
|
|
32
|
+
cleanupTimer = null;
|
|
33
|
+
initialized = false;
|
|
34
|
+
constructor(encryptionKey) {
|
|
35
|
+
this.storagePath = join(homedir(), '.sage', 'google_pending_auth.enc');
|
|
36
|
+
this.encryptionService = new EncryptionService({
|
|
37
|
+
encryptionKey: encryptionKey || process.env.SAGE_ENCRYPTION_KEY,
|
|
38
|
+
});
|
|
39
|
+
// Parse session timeout from environment or use default
|
|
40
|
+
const timeoutEnv = process.env.GOOGLE_AUTH_SESSION_TIMEOUT;
|
|
41
|
+
this.sessionTimeoutMs = timeoutEnv
|
|
42
|
+
? parseInt(timeoutEnv, 10) * 1000
|
|
43
|
+
: DEFAULT_SESSION_TIMEOUT_MS;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Initialize the store and start cleanup timer
|
|
47
|
+
*/
|
|
48
|
+
async initialize() {
|
|
49
|
+
if (this.initialized) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
await this.encryptionService.initialize();
|
|
53
|
+
await this.load();
|
|
54
|
+
// Start periodic cleanup
|
|
55
|
+
this.cleanupTimer = setInterval(() => {
|
|
56
|
+
this.cleanupExpired();
|
|
57
|
+
}, CLEANUP_INTERVAL_MS);
|
|
58
|
+
// Don't prevent process exit
|
|
59
|
+
this.cleanupTimer.unref();
|
|
60
|
+
this.initialized = true;
|
|
61
|
+
oauthLogger.info('PendingGoogleAuthStore initialized');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create a new pending auth session
|
|
65
|
+
*
|
|
66
|
+
* @param redirectUri - The OAuth callback URL
|
|
67
|
+
* @returns Session state, code_verifier, and code_challenge
|
|
68
|
+
*/
|
|
69
|
+
create(redirectUri) {
|
|
70
|
+
const state = randomUUID();
|
|
71
|
+
const codeVerifier = generateCodeVerifier();
|
|
72
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
const session = {
|
|
75
|
+
state,
|
|
76
|
+
codeVerifier,
|
|
77
|
+
redirectUri,
|
|
78
|
+
createdAt: now,
|
|
79
|
+
expiresAt: now + this.sessionTimeoutMs,
|
|
80
|
+
};
|
|
81
|
+
this.sessions.set(state, session);
|
|
82
|
+
// Persist asynchronously (don't block)
|
|
83
|
+
this.persist().catch(err => {
|
|
84
|
+
oauthLogger.error({ err }, 'Failed to persist pending auth session');
|
|
85
|
+
});
|
|
86
|
+
oauthLogger.info({ state }, 'Created pending auth session');
|
|
87
|
+
return {
|
|
88
|
+
state,
|
|
89
|
+
codeVerifier,
|
|
90
|
+
codeChallenge,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Find a session by state
|
|
95
|
+
*
|
|
96
|
+
* @param state - The session state (UUID)
|
|
97
|
+
* @returns The session or null if not found/expired
|
|
98
|
+
*/
|
|
99
|
+
findByState(state) {
|
|
100
|
+
const session = this.sessions.get(state);
|
|
101
|
+
if (!session) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
// Check expiration
|
|
105
|
+
if (Date.now() > session.expiresAt) {
|
|
106
|
+
this.sessions.delete(state);
|
|
107
|
+
oauthLogger.info({ state }, 'Session expired');
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return session;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Remove a session by state
|
|
114
|
+
*
|
|
115
|
+
* @param state - The session state to remove
|
|
116
|
+
*/
|
|
117
|
+
remove(state) {
|
|
118
|
+
const existed = this.sessions.delete(state);
|
|
119
|
+
if (existed) {
|
|
120
|
+
oauthLogger.info({ state }, 'Removed pending auth session');
|
|
121
|
+
// Persist asynchronously
|
|
122
|
+
this.persist().catch(err => {
|
|
123
|
+
oauthLogger.error({ err }, 'Failed to persist after session removal');
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Clean up expired sessions
|
|
129
|
+
*/
|
|
130
|
+
cleanupExpired() {
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
let removedCount = 0;
|
|
133
|
+
for (const [state, session] of this.sessions.entries()) {
|
|
134
|
+
if (now > session.expiresAt) {
|
|
135
|
+
this.sessions.delete(state);
|
|
136
|
+
removedCount++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (removedCount > 0) {
|
|
140
|
+
oauthLogger.info({ removedCount }, 'Cleaned up expired sessions');
|
|
141
|
+
// Persist asynchronously
|
|
142
|
+
this.persist().catch(err => {
|
|
143
|
+
oauthLogger.error({ err }, 'Failed to persist after cleanup');
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get session timeout in seconds
|
|
149
|
+
*/
|
|
150
|
+
getSessionTimeoutSeconds() {
|
|
151
|
+
return Math.floor(this.sessionTimeoutMs / 1000);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Persist sessions to encrypted file
|
|
155
|
+
*/
|
|
156
|
+
async persist() {
|
|
157
|
+
try {
|
|
158
|
+
const storage = {
|
|
159
|
+
version: 1,
|
|
160
|
+
sessions: Array.from(this.sessions.values()),
|
|
161
|
+
};
|
|
162
|
+
await this.encryptionService.encryptToFile(JSON.stringify(storage), this.storagePath);
|
|
163
|
+
oauthLogger.debug({ sessionCount: this.sessions.size }, 'Persisted pending auth sessions');
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
oauthLogger.error({ err: error }, 'Failed to persist pending auth sessions');
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Load sessions from encrypted file
|
|
172
|
+
*/
|
|
173
|
+
async load() {
|
|
174
|
+
try {
|
|
175
|
+
const data = await this.encryptionService.decryptFromFile(this.storagePath);
|
|
176
|
+
if (data === null) {
|
|
177
|
+
oauthLogger.debug('No existing pending auth sessions found');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const storage = JSON.parse(data);
|
|
181
|
+
if (storage.version !== 1) {
|
|
182
|
+
oauthLogger.warn({ version: storage.version }, 'Unknown storage version, ignoring');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Load sessions and filter out expired ones
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
for (const session of storage.sessions) {
|
|
188
|
+
if (now < session.expiresAt) {
|
|
189
|
+
this.sessions.set(session.state, session);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
oauthLogger.info({ sessionCount: this.sessions.size }, 'Loaded pending auth sessions');
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
oauthLogger.error({ err: error }, 'Failed to load pending auth sessions');
|
|
196
|
+
// Don't throw - start with empty sessions
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Shutdown the store and stop cleanup timer
|
|
201
|
+
*/
|
|
202
|
+
async shutdown() {
|
|
203
|
+
if (this.cleanupTimer) {
|
|
204
|
+
clearInterval(this.cleanupTimer);
|
|
205
|
+
this.cleanupTimer = null;
|
|
206
|
+
}
|
|
207
|
+
// Final persist
|
|
208
|
+
await this.persist();
|
|
209
|
+
oauthLogger.info('PendingGoogleAuthStore shutdown');
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get the number of active sessions
|
|
213
|
+
*/
|
|
214
|
+
getSessionCount() {
|
|
215
|
+
return this.sessions.size;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=pending-google-auth-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-google-auth-store.js","sourceRoot":"","sources":["../../src/oauth/pending-google-auth-store.ts"],"names":[],"mappings":"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"}
|