@chnxthr/knowledge-mcp 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +37 -2
- package/dist/bootstrap.d.ts +1 -0
- package/dist/bootstrap.js +18 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/auth.d.ts +38 -0
- package/dist/core/auth.js +93 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/config.d.ts +259 -0
- package/dist/core/config.js +172 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/context.d.ts +25 -0
- package/dist/core/context.js +37 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/env.d.ts +2 -0
- package/dist/core/env.js +5 -0
- package/dist/core/env.js.map +1 -0
- package/dist/core/log_mask.d.ts +10 -0
- package/dist/core/log_mask.js +88 -0
- package/dist/core/log_mask.js.map +1 -0
- package/dist/core/oauth.d.ts +46 -0
- package/dist/core/oauth.js +507 -0
- package/dist/core/oauth.js.map +1 -0
- package/dist/core/paths.d.ts +5 -0
- package/dist/core/paths.js +47 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/scope.d.ts +28 -0
- package/dist/core/scope.js +54 -0
- package/dist/core/scope.js.map +1 -0
- package/dist/core/upstream.d.ts +30 -0
- package/dist/core/upstream.js +164 -0
- package/dist/core/upstream.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/define.d.ts +144 -0
- package/dist/plugin/define.js +44 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/load.d.ts +2 -0
- package/dist/plugin/load.js +41 -0
- package/dist/plugin/load.js.map +1 -0
- package/dist/plugin/ping.d.ts +9 -0
- package/dist/plugin/ping.js +29 -0
- package/dist/plugin/ping.js.map +1 -0
- package/dist/plugin/registry.d.ts +62 -0
- package/dist/plugin/registry.js +248 -0
- package/dist/plugin/registry.js.map +1 -0
- package/dist/plugin/result.d.ts +20 -0
- package/dist/plugin/result.js +59 -0
- package/dist/plugin/result.js.map +1 -0
- package/dist/plugins/drive/chunker.d.ts +28 -0
- package/dist/plugins/drive/chunker.js +356 -0
- package/dist/plugins/drive/chunker.js.map +1 -0
- package/dist/plugins/drive/clean_text.d.ts +13 -0
- package/dist/plugins/drive/clean_text.js +31 -0
- package/dist/plugins/drive/clean_text.js.map +1 -0
- package/dist/plugins/drive/client.d.ts +25 -0
- package/dist/plugins/drive/client.js +62 -0
- package/dist/plugins/drive/client.js.map +1 -0
- package/dist/plugins/drive/embeddings.d.ts +15 -0
- package/dist/plugins/drive/embeddings.js +118 -0
- package/dist/plugins/drive/embeddings.js.map +1 -0
- package/dist/plugins/drive/exclude.d.ts +11 -0
- package/dist/plugins/drive/exclude.js +36 -0
- package/dist/plugins/drive/exclude.js.map +1 -0
- package/dist/plugins/drive/extract_text.d.ts +26 -0
- package/dist/plugins/drive/extract_text.js +108 -0
- package/dist/plugins/drive/extract_text.js.map +1 -0
- package/dist/plugins/drive/get_token.d.ts +5 -0
- package/dist/plugins/drive/get_token.js +113 -0
- package/dist/plugins/drive/get_token.js.map +1 -0
- package/dist/plugins/drive/index.d.ts +5 -0
- package/dist/plugins/drive/index.js +72 -0
- package/dist/plugins/drive/index.js.map +1 -0
- package/dist/plugins/drive/indexer.d.ts +69 -0
- package/dist/plugins/drive/indexer.js +204 -0
- package/dist/plugins/drive/indexer.js.map +1 -0
- package/dist/plugins/drive/mathpix.d.ts +4 -0
- package/dist/plugins/drive/mathpix.js +102 -0
- package/dist/plugins/drive/mathpix.js.map +1 -0
- package/dist/plugins/drive/path.d.ts +40 -0
- package/dist/plugins/drive/path.js +87 -0
- package/dist/plugins/drive/path.js.map +1 -0
- package/dist/plugins/drive/qdrant.d.ts +50 -0
- package/dist/plugins/drive/qdrant.js +61 -0
- package/dist/plugins/drive/qdrant.js.map +1 -0
- package/dist/plugins/drive/reindex.d.ts +2 -0
- package/dist/plugins/drive/reindex.js +263 -0
- package/dist/plugins/drive/reindex.js.map +1 -0
- package/dist/plugins/drive/settings.d.ts +11 -0
- package/dist/plugins/drive/settings.js +13 -0
- package/dist/plugins/drive/settings.js.map +1 -0
- package/dist/plugins/drive/sync.d.ts +16 -0
- package/dist/plugins/drive/sync.js +139 -0
- package/dist/plugins/drive/sync.js.map +1 -0
- package/dist/plugins/drive/sync_state.d.ts +3 -0
- package/dist/plugins/drive/sync_state.js +26 -0
- package/dist/plugins/drive/sync_state.js.map +1 -0
- package/dist/plugins/drive/tools/list_drive_folder.d.ts +104 -0
- package/dist/plugins/drive/tools/list_drive_folder.js +221 -0
- package/dist/plugins/drive/tools/list_drive_folder.js.map +1 -0
- package/dist/plugins/drive/tools/read_drive_file.d.ts +66 -0
- package/dist/plugins/drive/tools/read_drive_file.js +206 -0
- package/dist/plugins/drive/tools/read_drive_file.js.map +1 -0
- package/dist/plugins/drive/tools/search_drive.d.ts +52 -0
- package/dist/plugins/drive/tools/search_drive.js +78 -0
- package/dist/plugins/drive/tools/search_drive.js.map +1 -0
- package/dist/plugins/drive/tools/semantic_search_drive.d.ts +109 -0
- package/dist/plugins/drive/tools/semantic_search_drive.js +228 -0
- package/dist/plugins/drive/tools/semantic_search_drive.js.map +1 -0
- package/dist/plugins/github/index.d.ts +1 -0
- package/dist/plugins/github/index.js +27 -0
- package/dist/plugins/github/index.js.map +1 -0
- package/dist/plugins/github/repo_tree.d.ts +57 -0
- package/dist/plugins/github/repo_tree.js +123 -0
- package/dist/plugins/github/repo_tree.js.map +1 -0
- package/dist/plugins/index.d.ts +11 -0
- package/dist/plugins/index.js +7 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/railway/index.d.ts +1 -0
- package/dist/plugins/railway/index.js +21 -0
- package/dist/plugins/railway/index.js.map +1 -0
- package/dist/plugins/railway/tools.d.ts +114 -0
- package/dist/plugins/railway/tools.js +165 -0
- package/dist/plugins/railway/tools.js.map +1 -0
- package/dist/plugins/slack/api.d.ts +63 -0
- package/dist/plugins/slack/api.js +280 -0
- package/dist/plugins/slack/api.js.map +1 -0
- package/dist/plugins/slack/index.d.ts +1 -0
- package/dist/plugins/slack/index.js +80 -0
- package/dist/plugins/slack/index.js.map +1 -0
- package/dist/plugins/slack/tools/slack_files.d.ts +87 -0
- package/dist/plugins/slack/tools/slack_files.js +204 -0
- package/dist/plugins/slack/tools/slack_files.js.map +1 -0
- package/dist/plugins/slack/tools/slack_list_channels.d.ts +46 -0
- package/dist/plugins/slack/tools/slack_list_channels.js +197 -0
- package/dist/plugins/slack/tools/slack_list_channels.js.map +1 -0
- package/dist/plugins/slack/tools/slack_read_channel.d.ts +56 -0
- package/dist/plugins/slack/tools/slack_read_channel.js +150 -0
- package/dist/plugins/slack/tools/slack_read_channel.js.map +1 -0
- package/dist/plugins/slack/tools/slack_search.d.ts +53 -0
- package/dist/plugins/slack/tools/slack_search.js +132 -0
- package/dist/plugins/slack/tools/slack_search.js.map +1 -0
- package/dist/run.d.ts +8 -0
- package/dist/run.js +270 -0
- package/dist/run.js.map +1 -0
- package/dist/server/http.d.ts +5 -0
- package/dist/server/http.js +406 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/identity.d.ts +17 -0
- package/dist/server/identity.js +47 -0
- package/dist/server/identity.js.map +1 -0
- package/dist/server/mcp.d.ts +7 -0
- package/dist/server/mcp.js +55 -0
- package/dist/server/mcp.js.map +1 -0
- package/dist/server/stdio.d.ts +2 -0
- package/dist/server/stdio.js +40 -0
- package/dist/server/stdio.js.map +1 -0
- package/dist/store/index.d.ts +11 -0
- package/dist/store/index.js +31 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/keys.d.ts +9 -0
- package/dist/store/keys.js +32 -0
- package/dist/store/keys.js.map +1 -0
- package/dist/store/sqlite.d.ts +38 -0
- package/dist/store/sqlite.js +316 -0
- package/dist/store/sqlite.js.map +1 -0
- package/dist/store/supabase.d.ts +35 -0
- package/dist/store/supabase.js +269 -0
- package/dist/store/supabase.js.map +1 -0
- package/dist/store/supabase_client.d.ts +10 -0
- package/dist/store/supabase_client.js +31 -0
- package/dist/store/supabase_client.js.map +1 -0
- package/dist/store/tool_log.d.ts +12 -0
- package/dist/store/tool_log.js +8 -0
- package/dist/store/tool_log.js.map +1 -0
- package/dist/store/types.d.ts +96 -0
- package/dist/store/types.js +2 -0
- package/dist/store/types.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/package.json +84 -4
- package/sql/supabase.sql +185 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Authorization Server + Resource Server (Route X) — Claude 앱 커스텀 커넥터용.
|
|
3
|
+
*
|
|
4
|
+
* 우리 MCP 서버가 AS 이자 RS(같은 프로세스)다:
|
|
5
|
+
* - AS: /authorize·/token·/register(.well-known 메타) 는 SDK `mcpAuthRouter` 가 깔고,
|
|
6
|
+
* 실제 로직은 이 파일의 `KnowledgeOAuthProvider`(OAuthServerProvider 구현)가 담당.
|
|
7
|
+
* - 동의: /authorize 는 바로 Google 로 보내지 않고 동의 화면을 먼저 보여 준다(어떤 앱이, 어디로 토큰을 받는지).
|
|
8
|
+
* DCR 로 누구나 client 를 등록할 수 있어, 동의 없이 넘기면 링크 한 번에 토큰이 공격자 redirect_uri 로 간다
|
|
9
|
+
* (MCP 보안 모범 사례의 confused deputy). 승인 버튼은 쿠키에 묶어 다른 사이트가 대신 누르지 못하게 한다.
|
|
10
|
+
* - 로그인: Google Workspace(OIDC)로 브리지. 허용 도메인(hd) 또는 허용 이메일 중 하나에 들어야 한다.
|
|
11
|
+
* - 토큰: access/refresh 를 HS256 JWT 로 자체 발급(AS=RS 라 JWKS 배포 불필요, 대칭키로 in-process 검증).
|
|
12
|
+
* access token 의 aud = canonical resource(=우리 /mcp). RFC 8707.
|
|
13
|
+
* - PKCE(S256): SDK token 핸들러가 challengeForAuthorizationCode() 로 받아 자체 검증.
|
|
14
|
+
*
|
|
15
|
+
* graceful: 필수 값이 없으면 isOAuthConfigured()=false → server/http.ts 가 라우터를 안 깐다(기존 Bearer 무손상).
|
|
16
|
+
* 허용 목록(도메인·이메일)도 필수 값이다. 둘 다 비면 아무 Google 계정이나 로그인되므로 OAuth 를 켜지 않는다.
|
|
17
|
+
*/
|
|
18
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
19
|
+
import { SignJWT, jwtVerify, createRemoteJWKSet } from 'jose';
|
|
20
|
+
import { getStore } from '../store/index.js';
|
|
21
|
+
import { getConfig } from './config.js';
|
|
22
|
+
import { timingSafeEqualStr } from './auth.js';
|
|
23
|
+
import { InvalidGrantError, InvalidScopeError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
|
|
24
|
+
// ── 설정 (env) ──
|
|
25
|
+
const ISSUER = (process.env.MCP_OAUTH_ISSUER || '').replace(/\/$/, ''); // e.g. https://mcp.example.com
|
|
26
|
+
const RESOURCE = (process.env.MCP_RESOURCE || (ISSUER ? `${ISSUER}/mcp` : '')).replace(/\/$/, ''); // canonical, = token aud
|
|
27
|
+
// 로그인 브리지용 Google 클라이언트. 전용 OAUTH_GOOGLE_* 우선, 없으면 기존 Drive용 GOOGLE_OAUTH_* 재사용.
|
|
28
|
+
const GOOGLE_CLIENT_ID = process.env.OAUTH_GOOGLE_CLIENT_ID || process.env.GOOGLE_OAUTH_CLIENT_ID || '';
|
|
29
|
+
const GOOGLE_CLIENT_SECRET = process.env.OAUTH_GOOGLE_CLIENT_SECRET || process.env.GOOGLE_OAUTH_CLIENT_SECRET || '';
|
|
30
|
+
const JWT_SECRET = process.env.OAUTH_JWT_SECRET || '';
|
|
31
|
+
const csv = (v) => v.split(',');
|
|
32
|
+
const ALLOWED_DOMAINS = (process.env.OAUTH_ALLOWED_HD ? csv(process.env.OAUTH_ALLOWED_HD) : getConfig().oauth.allowedDomains)
|
|
33
|
+
.map((s) => s.trim().toLowerCase())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
const ALLOWED_EMAILS = (process.env.OAUTH_ALLOWED_EMAILS ? csv(process.env.OAUTH_ALLOWED_EMAILS) : getConfig().oauth.allowedEmails)
|
|
36
|
+
.map((s) => s.trim().toLowerCase())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
const emailDomain = (e) => e.slice(e.lastIndexOf('@') + 1);
|
|
39
|
+
/**
|
|
40
|
+
* 계정 허용 판정 — 허용 이메일 목록에 있거나, 이메일 도메인이 허용 도메인이면 허용(합집합).
|
|
41
|
+
* 둘 다 비어 있으면 아무도 허용하지 않는다(fail-closed). 그 경우 OAuth 자체가 켜지지 않는다.
|
|
42
|
+
* 로그인뿐 아니라 **토큰 검증·refresh 마다 재확인**해서, 허용 목록에서 빠진 계정은
|
|
43
|
+
* 기존 토큰(특히 30일 refresh)이 남아 있어도 즉시 차단되도록 한다.
|
|
44
|
+
*/
|
|
45
|
+
function emailAllowed(email) {
|
|
46
|
+
const e = (email || '').toLowerCase();
|
|
47
|
+
if (!e || !e.includes('@'))
|
|
48
|
+
return false;
|
|
49
|
+
return ALLOWED_EMAILS.includes(e) || ALLOWED_DOMAINS.includes(emailDomain(e));
|
|
50
|
+
}
|
|
51
|
+
const GOOGLE_CALLBACK = ISSUER ? `${ISSUER}/oauth/google/callback` : '';
|
|
52
|
+
const SCOPE = 'mcp';
|
|
53
|
+
const ACCESS_TTL = 3600; // 1h
|
|
54
|
+
const REFRESH_TTL = 30 * 24 * 3600; // 30d
|
|
55
|
+
const CODE_TTL = 300; // 5m
|
|
56
|
+
const STATE_TTL = 600; // 10m
|
|
57
|
+
const CONSENT_TTL = 600; // 10m
|
|
58
|
+
export const CONSENT_COOKIE = 'kmcp_consent';
|
|
59
|
+
export const CONSENT_PATH = '/oauth/consent';
|
|
60
|
+
const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
61
|
+
const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
62
|
+
const GOOGLE_JWKS = createRemoteJWKSet(new URL('https://www.googleapis.com/oauth2/v3/certs'));
|
|
63
|
+
/** OAuth 를 켜는 데 빠진 값 목록 (이름만, 값은 노출 안 함). 빈 배열이면 켤 수 있다. */
|
|
64
|
+
export function oauthConfigProblems() {
|
|
65
|
+
const out = [];
|
|
66
|
+
if (!ISSUER)
|
|
67
|
+
out.push('MCP_OAUTH_ISSUER');
|
|
68
|
+
if (!GOOGLE_CLIENT_ID)
|
|
69
|
+
out.push('OAUTH_GOOGLE_CLIENT_ID');
|
|
70
|
+
if (!GOOGLE_CLIENT_SECRET)
|
|
71
|
+
out.push('OAUTH_GOOGLE_CLIENT_SECRET');
|
|
72
|
+
if (JWT_SECRET.length < 32)
|
|
73
|
+
out.push('OAUTH_JWT_SECRET (>= 32 chars)');
|
|
74
|
+
if (ALLOWED_DOMAINS.length === 0 && ALLOWED_EMAILS.length === 0) {
|
|
75
|
+
out.push('OAUTH_ALLOWED_HD or OAUTH_ALLOWED_EMAILS (config oauth.allowedDomains / allowedEmails)');
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
/** OAuth 를 하나라도 설정하려 한 흔적이 있는가 — 반쯤 설정된 상태를 부팅 로그에서 크게 알리기 위함. */
|
|
80
|
+
export function oauthAttempted() {
|
|
81
|
+
return Boolean(ISSUER || process.env.OAUTH_GOOGLE_CLIENT_ID || JWT_SECRET);
|
|
82
|
+
}
|
|
83
|
+
/** OAuth 활성 조건 — 필수 값이 모두 있어야 함. 없으면 index 가 라우터 미장착(기존 Bearer 유지). */
|
|
84
|
+
export function isOAuthConfigured() {
|
|
85
|
+
return Boolean(RESOURCE) && oauthConfigProblems().length === 0;
|
|
86
|
+
}
|
|
87
|
+
export const OAUTH_ISSUER_URL = ISSUER;
|
|
88
|
+
export const OAUTH_RESOURCE = RESOURCE;
|
|
89
|
+
export const OAUTH_SCOPES = [SCOPE];
|
|
90
|
+
const jwtKey = () => new TextEncoder().encode(JWT_SECRET);
|
|
91
|
+
const _authCodes = new Map();
|
|
92
|
+
const _googlePending = new Map();
|
|
93
|
+
const _consentPending = new Map();
|
|
94
|
+
function escapeHtml(s) {
|
|
95
|
+
return s.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
|
96
|
+
}
|
|
97
|
+
/** 동의 화면 HTML. 앱 이름과 토큰이 돌아갈 주소를 그대로 보여 준다. */
|
|
98
|
+
function consentPage(opts) {
|
|
99
|
+
const server = escapeHtml(getConfig().branding.name);
|
|
100
|
+
const app = escapeHtml(opts.appName);
|
|
101
|
+
let target = opts.redirectUri;
|
|
102
|
+
try {
|
|
103
|
+
const u = new URL(opts.redirectUri);
|
|
104
|
+
target = `${u.protocol}//${u.host}${u.pathname}`;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* 등록 단계에서 URL 검증됨 — 그래도 원문 표시 */
|
|
108
|
+
}
|
|
109
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
110
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"><title>${server} sign-in</title>
|
|
111
|
+
<style>
|
|
112
|
+
body{margin:0;font:16px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#f6f6f4;color:#111}
|
|
113
|
+
main{max-width:440px;margin:12vh auto;padding:32px;background:#fff;border:1px solid #e3e3df;border-radius:14px}
|
|
114
|
+
h1{font-size:20px;margin:0 0 16px}p{margin:0 0 12px;color:#444}code{word-break:break-all;background:#f1f1ee;padding:2px 6px;border-radius:6px}
|
|
115
|
+
.row{display:flex;gap:12px;margin-top:24px}button{flex:1;font:inherit;padding:10px 14px;border-radius:10px;border:1px solid #c8cbd0;background:#fff;cursor:pointer}
|
|
116
|
+
button[value=approve]{background:#111;color:#fff;border-color:#111}
|
|
117
|
+
</style></head><body><main>
|
|
118
|
+
<h1>${app} wants to use ${server}</h1>
|
|
119
|
+
<p>If you continue, you sign in with Google and this app receives a token that can read what your account can read.</p>
|
|
120
|
+
<p>The token is sent to:<br><code>${escapeHtml(target)}</code></p>
|
|
121
|
+
<p>Client ID: <code>${escapeHtml(opts.clientId)}</code></p>
|
|
122
|
+
<p>Continue only if you started this connection yourself.</p>
|
|
123
|
+
<form method="post" action="${escapeHtml(ISSUER + CONSENT_PATH)}">
|
|
124
|
+
<input type="hidden" name="id" value="${escapeHtml(opts.id)}">
|
|
125
|
+
<input type="hidden" name="csrf" value="${escapeHtml(opts.csrf)}">
|
|
126
|
+
<div class="row"><button type="submit" name="action" value="deny">Cancel</button>
|
|
127
|
+
<button type="submit" name="action" value="approve">Continue with Google</button></div>
|
|
128
|
+
</form></main></body></html>`;
|
|
129
|
+
}
|
|
130
|
+
function sweep(m) {
|
|
131
|
+
const now = Date.now() / 1000;
|
|
132
|
+
for (const [k, v] of m)
|
|
133
|
+
if (v.exp <= now)
|
|
134
|
+
m.delete(k);
|
|
135
|
+
}
|
|
136
|
+
// ── 클라이언트 스토어 (DCR — Claude/ChatGPT 자동 등록) ──
|
|
137
|
+
// 저장소(SQLite 파일 또는 Supabase mcp_oauth_clients) 영속화 + 인메모리 캐시.
|
|
138
|
+
// 왜: 예전 순수 인메모리 Map 은 서버 재시작(재배포)마다 등록 클라이언트를 통째로 잃어, 커넥터가
|
|
139
|
+
// 옛 client_id 로 재접속 시 "invalid_client" 를 냈다(특히 ChatGPT — 자동 재등록 안 함). DB 를
|
|
140
|
+
// 정본으로 두면 재시작에도 살아남는다. 캐시는 핫패스(매 authorize/token 의 getClient)용.
|
|
141
|
+
// graceful: DB 불가(마이그레이션 전 배포·일시 장애)면 캐시로 폴백 → 그 프로세스 세션 동안만 유효
|
|
142
|
+
// (기존 인메모리 동작과 동일). 배포 순서에 안전 — 테이블 없어도 안 깨진다.
|
|
143
|
+
class ClientsStore {
|
|
144
|
+
cache = new Map();
|
|
145
|
+
static CACHE_MAX = 500; // 캐시 cap — 정본은 DB 라 evict 돼도 재조회로 복구(안전)
|
|
146
|
+
putCache(client) {
|
|
147
|
+
while (this.cache.size >= ClientsStore.CACHE_MAX) {
|
|
148
|
+
const oldest = this.cache.keys().next().value;
|
|
149
|
+
if (oldest === undefined)
|
|
150
|
+
break;
|
|
151
|
+
this.cache.delete(oldest);
|
|
152
|
+
}
|
|
153
|
+
this.cache.set(client.client_id, client);
|
|
154
|
+
}
|
|
155
|
+
async getClient(clientId) {
|
|
156
|
+
const cached = this.cache.get(clientId);
|
|
157
|
+
if (cached)
|
|
158
|
+
return cached;
|
|
159
|
+
try {
|
|
160
|
+
const store = await getStore();
|
|
161
|
+
const client = await store.getOAuthClient(clientId);
|
|
162
|
+
if (!client)
|
|
163
|
+
return undefined;
|
|
164
|
+
this.putCache(client);
|
|
165
|
+
// last_used 갱신 (best-effort — cleanup 판단용, 실패 무시)
|
|
166
|
+
void store.touchOAuthClient(clientId).catch((e) => {
|
|
167
|
+
console.error('[oauth] client last_used 갱신 실패:', e instanceof Error ? e.message : e);
|
|
168
|
+
});
|
|
169
|
+
return client;
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
console.error('[oauth] client 조회 실패(캐시 폴백):', e instanceof Error ? e.message : e);
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// register 핸들러가 client_id/secret 를 이미 생성해 넘김 → 캐시 + DB 저장.
|
|
177
|
+
async registerClient(client) {
|
|
178
|
+
this.putCache(client); // 즉시 사용 가능
|
|
179
|
+
try {
|
|
180
|
+
await (await getStore()).saveOAuthClient(client);
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
// fail-open: 저장 실패해도 등록은 성공(그 세션 동안 캐시로 유효). 커넥터 설정을 하드 실패시키는
|
|
184
|
+
// 것보단 낫다(저장소 장애는 드묾). 경고 로그로 노출 — 재시작 시 유실될 수 있음.
|
|
185
|
+
console.error('[oauth] client 저장 실패(메모리만 유지, 재시작 시 유실 가능):', e instanceof Error ? e.message : e);
|
|
186
|
+
}
|
|
187
|
+
return client;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// ── Google id_token 검증 (hd + email allowlist 로 사내 제한) ──
|
|
191
|
+
async function verifyGoogleIdToken(idToken) {
|
|
192
|
+
const { payload } = await jwtVerify(idToken, GOOGLE_JWKS, {
|
|
193
|
+
issuer: ['https://accounts.google.com', 'accounts.google.com'],
|
|
194
|
+
audience: GOOGLE_CLIENT_ID,
|
|
195
|
+
algorithms: ['RS256'], // alg 고정 (defense-in-depth)
|
|
196
|
+
});
|
|
197
|
+
const email = String(payload.email || '').toLowerCase();
|
|
198
|
+
const hd = payload.hd ? String(payload.hd).toLowerCase() : undefined;
|
|
199
|
+
const emailVerified = payload.email_verified === true || payload.email_verified === 'true';
|
|
200
|
+
if (!email || !emailVerified)
|
|
201
|
+
throw new Error('google: email 미검증');
|
|
202
|
+
// 허용 이메일 목록에 있으면 계정 종류와 무관하게 허용(외부 협력자 등).
|
|
203
|
+
if (ALLOWED_EMAILS.includes(email))
|
|
204
|
+
return { email, hd };
|
|
205
|
+
// 도메인으로 허용할 때는 hd 클레임이 그 도메인이어야 한다 — Workspace 가 관리하는 계정만.
|
|
206
|
+
// (hd 파라미터는 힌트일 뿐이라 신뢰 불가. 회사 주소로 만든 개인 Google 계정은 hd 가 없다.)
|
|
207
|
+
const domain = emailDomain(email);
|
|
208
|
+
if (ALLOWED_DOMAINS.includes(domain) && hd === domain)
|
|
209
|
+
return { email, hd };
|
|
210
|
+
throw new Error(`google: 허용되지 않은 계정(hd=${hd ?? 'none'})`);
|
|
211
|
+
}
|
|
212
|
+
// ── refresh token 상태 (저장소 mcp_refresh_tokens) ──
|
|
213
|
+
// 왜: HS256 JWT 는 무상태라 rotation 해도 옛 refresh 가 만료(30일)까지 유효했다(P2 — 탈취 시 차단 불가).
|
|
214
|
+
// jti 를 저장소에 기록해 rotation 시 옛 토큰을 즉시 폐기하고, 폐기된 토큰 재사용(탈취 신호)이 오면
|
|
215
|
+
// 그 계정의 활성 refresh 전부를 강제 폐기한다. access token(1h)은 무상태 유지 — 요청 지연 없음.
|
|
216
|
+
/** 발급한 refresh jti 기록. 만료 row 도 김에 정리. 실패 시 throw — 기록 안 된 토큰은 refresh 불가라 발급 자체를 실패시킨다. */
|
|
217
|
+
async function recordRefreshToken(jti, email, clientId) {
|
|
218
|
+
const store = await getStore();
|
|
219
|
+
try {
|
|
220
|
+
await store.recordRefreshToken(jti, email, clientId, new Date(Date.now() + REFRESH_TTL * 1000).toISOString());
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
throw new Error(`refresh 토큰 기록 실패: ${e instanceof Error ? e.message : String(e)}`);
|
|
224
|
+
}
|
|
225
|
+
void store.purgeExpiredRefreshTokens().catch((e) => {
|
|
226
|
+
console.error('[oauth] refresh 만료분 정리 실패:', e instanceof Error ? e.message : e);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* refresh jti 소비(rotation) — 활성 row 만 원자적으로 폐기 표시.
|
|
231
|
+
* 'ok' : 정상 — 폐기 완료, 새 토큰 발급 진행
|
|
232
|
+
* 'reused' : 이미 폐기된 jti 재사용(탈취 신호) → 계정 전체 refresh 폐기됨
|
|
233
|
+
* 'unknown' : 기록에 없음(구형 토큰·저장소 유실) → 거부(재로그인)
|
|
234
|
+
* 저장소 오류는 throw — fail-closed (revocation 체크를 못 하면 refresh 를 내주지 않는다).
|
|
235
|
+
*/
|
|
236
|
+
async function consumeRefreshToken(jti, email, rotatedTo) {
|
|
237
|
+
const store = await getStore();
|
|
238
|
+
let state;
|
|
239
|
+
try {
|
|
240
|
+
state = await store.consumeRefreshToken(jti, rotatedTo);
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
throw new Error(`refresh 상태 확인 실패: ${e instanceof Error ? e.message : String(e)}`);
|
|
244
|
+
}
|
|
245
|
+
if (state !== 'revoked')
|
|
246
|
+
return state;
|
|
247
|
+
console.error(`[oauth] ⚠️ 폐기된 refresh 재사용 감지 — ${email} 의 활성 refresh 전부 폐기`);
|
|
248
|
+
try {
|
|
249
|
+
await store.revokeAllRefreshForEmail(email);
|
|
250
|
+
}
|
|
251
|
+
catch (e) {
|
|
252
|
+
console.error('[oauth] 계정 refresh 일괄 폐기 실패:', e instanceof Error ? e.message : e);
|
|
253
|
+
}
|
|
254
|
+
return 'reused';
|
|
255
|
+
}
|
|
256
|
+
async function signJwt(claims, ttl, typ) {
|
|
257
|
+
return new SignJWT({ ...claims, typ })
|
|
258
|
+
.setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
|
|
259
|
+
.setIssuer(ISSUER)
|
|
260
|
+
.setAudience(RESOURCE)
|
|
261
|
+
.setIssuedAt()
|
|
262
|
+
.setExpirationTime(`${ttl}s`)
|
|
263
|
+
.sign(jwtKey());
|
|
264
|
+
}
|
|
265
|
+
export class KnowledgeOAuthProvider {
|
|
266
|
+
_clients = new ClientsStore();
|
|
267
|
+
get clientsStore() {
|
|
268
|
+
return this._clients;
|
|
269
|
+
}
|
|
270
|
+
// PKCE 는 우리(AS)가 자체 검증 (Google 으로 넘기지 않음)
|
|
271
|
+
skipLocalPkceValidation = false;
|
|
272
|
+
/**
|
|
273
|
+
* authorize: 동의 화면을 먼저 보여 준다. 승인(POST /oauth/consent)이 와야 Google 로그인으로 넘긴다.
|
|
274
|
+
* 우리 code 는 Google 콜백에서 발급.
|
|
275
|
+
*/
|
|
276
|
+
async authorize(client, params, res) {
|
|
277
|
+
sweep(_consentPending);
|
|
278
|
+
const id = randomBytes(18).toString('base64url');
|
|
279
|
+
const csrf = randomBytes(24).toString('base64url');
|
|
280
|
+
_consentPending.set(id, {
|
|
281
|
+
clientId: client.client_id,
|
|
282
|
+
redirectUri: params.redirectUri,
|
|
283
|
+
codeChallenge: params.codeChallenge,
|
|
284
|
+
clientState: params.state,
|
|
285
|
+
scopes: params.scopes && params.scopes.length ? params.scopes : [SCOPE],
|
|
286
|
+
resource: params.resource?.href,
|
|
287
|
+
exp: Date.now() / 1000 + CONSENT_TTL,
|
|
288
|
+
csrf,
|
|
289
|
+
});
|
|
290
|
+
const secure = ISSUER.startsWith('https://') ? '; Secure' : '';
|
|
291
|
+
// SameSite=Lax: 다른 사이트가 자동 제출하는 POST 에는 쿠키가 실리지 않아 승인을 대신 누를 수 없다.
|
|
292
|
+
res.setHeader('Set-Cookie', `${CONSENT_COOKIE}=${csrf}; Path=${CONSENT_PATH}; Max-Age=${CONSENT_TTL}; HttpOnly; SameSite=Lax${secure}`);
|
|
293
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
294
|
+
res.setHeader('X-Frame-Options', 'DENY'); // 클릭재킹 방지 (승인 버튼을 투명 iframe 으로 누르게 하는 공격)
|
|
295
|
+
res.setHeader('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; base-uri 'none'");
|
|
296
|
+
res
|
|
297
|
+
.status(200)
|
|
298
|
+
.type('html')
|
|
299
|
+
.send(consentPage({
|
|
300
|
+
appName: typeof client.client_name === 'string' && client.client_name ? client.client_name : 'An app',
|
|
301
|
+
clientId: client.client_id,
|
|
302
|
+
redirectUri: params.redirectUri,
|
|
303
|
+
id,
|
|
304
|
+
csrf,
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* 동의 화면의 POST 처리 (server/http.ts 의 POST /oauth/consent 가 호출). 돌려줄 redirect URL 을 반환한다.
|
|
309
|
+
* 승인: 폼 csrf·쿠키 csrf·저장된 csrf 가 모두 같을 때만 Google 로그인으로. 거절: client 로 access_denied.
|
|
310
|
+
* 검증 실패는 throw — 호출부가 400 으로 끝낸다(공격자 redirect_uri 로 보내지 않는다).
|
|
311
|
+
*/
|
|
312
|
+
consumeConsent(id, formCsrf, cookieCsrf, approve) {
|
|
313
|
+
sweep(_consentPending);
|
|
314
|
+
const pending = _consentPending.get(id);
|
|
315
|
+
if (!pending)
|
|
316
|
+
throw new Error('알 수 없거나 만료된 요청입니다. 연결을 처음부터 다시 시작해 주세요.');
|
|
317
|
+
if (!formCsrf ||
|
|
318
|
+
!cookieCsrf ||
|
|
319
|
+
!timingSafeEqualStr(formCsrf, pending.csrf) ||
|
|
320
|
+
!timingSafeEqualStr(cookieCsrf, pending.csrf)) {
|
|
321
|
+
throw new Error('요청을 확인할 수 없습니다. 연결을 처음부터 다시 시작해 주세요.');
|
|
322
|
+
}
|
|
323
|
+
_consentPending.delete(id);
|
|
324
|
+
if (!approve) {
|
|
325
|
+
const u = new URL(pending.redirectUri);
|
|
326
|
+
u.searchParams.set('error', 'access_denied');
|
|
327
|
+
u.searchParams.set('error_description', 'user cancelled');
|
|
328
|
+
if (pending.clientState)
|
|
329
|
+
u.searchParams.set('state', pending.clientState);
|
|
330
|
+
return u.toString();
|
|
331
|
+
}
|
|
332
|
+
const { csrf: _csrf, ...rest } = pending;
|
|
333
|
+
return this.googleRedirect(rest);
|
|
334
|
+
}
|
|
335
|
+
/** Google 로그인 URL 을 만들고 state 를 기록한다. */
|
|
336
|
+
googleRedirect(pending) {
|
|
337
|
+
sweep(_googlePending);
|
|
338
|
+
const state = randomBytes(24).toString('base64url');
|
|
339
|
+
_googlePending.set(state, { ...pending, exp: Date.now() / 1000 + STATE_TTL });
|
|
340
|
+
const u = new URL(GOOGLE_AUTH_URL);
|
|
341
|
+
u.searchParams.set('client_id', GOOGLE_CLIENT_ID);
|
|
342
|
+
u.searchParams.set('redirect_uri', GOOGLE_CALLBACK);
|
|
343
|
+
u.searchParams.set('response_type', 'code');
|
|
344
|
+
u.searchParams.set('scope', 'openid email profile');
|
|
345
|
+
u.searchParams.set('access_type', 'online');
|
|
346
|
+
u.searchParams.set('prompt', 'select_account');
|
|
347
|
+
// hd 는 계정 선택 화면 힌트일 뿐(검증은 id_token 에서). 허용 도메인이 하나이고 이메일 목록이 없을 때만 붙인다.
|
|
348
|
+
if (ALLOWED_DOMAINS.length === 1 && ALLOWED_EMAILS.length === 0)
|
|
349
|
+
u.searchParams.set('hd', ALLOWED_DOMAINS[0]);
|
|
350
|
+
u.searchParams.set('state', state);
|
|
351
|
+
return u.toString();
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Google 콜백 처리 (server/http.ts 의 /oauth/google/callback 라우트가 호출).
|
|
355
|
+
* 성공/실패 모두 client redirect_uri 로 돌려보낼 URL 을 반환한다.
|
|
356
|
+
*/
|
|
357
|
+
async completeGoogleLogin(googleCode, state) {
|
|
358
|
+
sweep(_googlePending);
|
|
359
|
+
const pending = _googlePending.get(state);
|
|
360
|
+
if (!pending)
|
|
361
|
+
throw new Error('알 수 없거나 만료된 state');
|
|
362
|
+
_googlePending.delete(state);
|
|
363
|
+
const redirectErr = (err, desc) => {
|
|
364
|
+
const u = new URL(pending.redirectUri);
|
|
365
|
+
u.searchParams.set('error', err);
|
|
366
|
+
u.searchParams.set('error_description', desc);
|
|
367
|
+
if (pending.clientState)
|
|
368
|
+
u.searchParams.set('state', pending.clientState);
|
|
369
|
+
return u.toString();
|
|
370
|
+
};
|
|
371
|
+
let email;
|
|
372
|
+
try {
|
|
373
|
+
// Google code → id_token 교환
|
|
374
|
+
const body = new URLSearchParams({
|
|
375
|
+
code: googleCode,
|
|
376
|
+
client_id: GOOGLE_CLIENT_ID,
|
|
377
|
+
client_secret: GOOGLE_CLIENT_SECRET,
|
|
378
|
+
redirect_uri: GOOGLE_CALLBACK,
|
|
379
|
+
grant_type: 'authorization_code',
|
|
380
|
+
});
|
|
381
|
+
const r = await fetch(GOOGLE_TOKEN_URL, {
|
|
382
|
+
method: 'POST',
|
|
383
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
384
|
+
body: body.toString(),
|
|
385
|
+
});
|
|
386
|
+
const j = (await r.json());
|
|
387
|
+
if (!r.ok || !j.id_token)
|
|
388
|
+
throw new Error(j.error_description || j.error || `google token ${r.status}`);
|
|
389
|
+
({ email } = await verifyGoogleIdToken(j.id_token));
|
|
390
|
+
}
|
|
391
|
+
catch (e) {
|
|
392
|
+
return redirectErr('access_denied', e instanceof Error ? e.message : '로그인 실패');
|
|
393
|
+
}
|
|
394
|
+
// 우리 authorization code 발급 (one-time, 짧은 TTL)
|
|
395
|
+
sweep(_authCodes);
|
|
396
|
+
const code = randomBytes(24).toString('base64url');
|
|
397
|
+
_authCodes.set(code, {
|
|
398
|
+
codeChallenge: pending.codeChallenge,
|
|
399
|
+
clientId: pending.clientId,
|
|
400
|
+
redirectUri: pending.redirectUri,
|
|
401
|
+
email,
|
|
402
|
+
scopes: pending.scopes,
|
|
403
|
+
resource: pending.resource,
|
|
404
|
+
exp: Date.now() / 1000 + CODE_TTL,
|
|
405
|
+
});
|
|
406
|
+
const u = new URL(pending.redirectUri);
|
|
407
|
+
u.searchParams.set('code', code);
|
|
408
|
+
if (pending.clientState)
|
|
409
|
+
u.searchParams.set('state', pending.clientState);
|
|
410
|
+
return u.toString();
|
|
411
|
+
}
|
|
412
|
+
async challengeForAuthorizationCode(_client, authorizationCode) {
|
|
413
|
+
const entry = _authCodes.get(authorizationCode);
|
|
414
|
+
if (!entry || entry.exp <= Date.now() / 1000)
|
|
415
|
+
throw new InvalidGrantError('authorization code 없음/만료');
|
|
416
|
+
return entry.codeChallenge;
|
|
417
|
+
}
|
|
418
|
+
async exchangeAuthorizationCode(client, authorizationCode, _codeVerifier, // SDK 가 challengeForAuthorizationCode 와 PKCE 검증 완료
|
|
419
|
+
redirectUri, _resource) {
|
|
420
|
+
const entry = _authCodes.get(authorizationCode);
|
|
421
|
+
if (!entry || entry.exp <= Date.now() / 1000)
|
|
422
|
+
throw new InvalidGrantError('authorization code 없음/만료');
|
|
423
|
+
_authCodes.delete(authorizationCode); // one-time
|
|
424
|
+
if (entry.clientId !== client.client_id)
|
|
425
|
+
throw new InvalidGrantError('client 불일치');
|
|
426
|
+
if (redirectUri && redirectUri !== entry.redirectUri)
|
|
427
|
+
throw new InvalidGrantError('redirect_uri 불일치');
|
|
428
|
+
const scope = entry.scopes.join(' ') || SCOPE;
|
|
429
|
+
const claims = { sub: entry.email, email: entry.email, scope, client_id: client.client_id };
|
|
430
|
+
const access_token = await signJwt(claims, ACCESS_TTL, 'access');
|
|
431
|
+
// refresh 는 jti 발급 + DB 기록 — rotation 시 즉시 폐기 가능하게 (P2)
|
|
432
|
+
const jti = randomUUID();
|
|
433
|
+
const refresh_token = await signJwt({ ...claims, jti }, REFRESH_TTL, 'refresh');
|
|
434
|
+
await recordRefreshToken(jti, entry.email, client.client_id);
|
|
435
|
+
return { access_token, token_type: 'Bearer', expires_in: ACCESS_TTL, scope, refresh_token };
|
|
436
|
+
}
|
|
437
|
+
async exchangeRefreshToken(client, refreshToken, scopes, _resource) {
|
|
438
|
+
let email = '';
|
|
439
|
+
let jti = '';
|
|
440
|
+
let tokenClientId = '';
|
|
441
|
+
let grantedScopes = [];
|
|
442
|
+
try {
|
|
443
|
+
const { payload } = await jwtVerify(refreshToken, jwtKey(), {
|
|
444
|
+
issuer: ISSUER,
|
|
445
|
+
audience: RESOURCE,
|
|
446
|
+
algorithms: ['HS256'],
|
|
447
|
+
});
|
|
448
|
+
if (payload.typ !== 'refresh')
|
|
449
|
+
throw new Error('not a refresh token');
|
|
450
|
+
email = String(payload.email || payload.sub || '');
|
|
451
|
+
jti = typeof payload.jti === 'string' ? payload.jti : '';
|
|
452
|
+
tokenClientId = typeof payload.client_id === 'string' ? payload.client_id : '';
|
|
453
|
+
grantedScopes = String(payload.scope || SCOPE).split(' ').filter(Boolean);
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
throw new InvalidGrantError('refresh token 무효');
|
|
457
|
+
}
|
|
458
|
+
// refresh token 은 발급받은 client 만 쓸 수 있다 — 새어 나간 토큰을 다른(공격자가 등록한) client 로 바꾸지 못하게.
|
|
459
|
+
if (!tokenClientId || tokenClientId !== client.client_id)
|
|
460
|
+
throw new InvalidGrantError('refresh token 의 client 불일치');
|
|
461
|
+
// 처음 허용된 범위보다 넓힐 수 없다.
|
|
462
|
+
if (scopes && scopes.some((s) => !grantedScopes.includes(s)))
|
|
463
|
+
throw new InvalidScopeError('요청 scope 가 원래 범위를 넘습니다');
|
|
464
|
+
// refresh 시 허용 목록 재확인 — 빠진 계정은 30일 refresh 가 남아도 즉시 차단.
|
|
465
|
+
if (!emailAllowed(email))
|
|
466
|
+
throw new InvalidGrantError('계정이 더 이상 허용되지 않음');
|
|
467
|
+
// jti 없는 구형 refresh 는 거부(재로그인 1회 유도). jti 는 DB 에서 소비(rotation) —
|
|
468
|
+
// 옛 토큰 즉시 폐기, 폐기된 토큰 재사용(탈취 신호)이면 계정 전체 refresh 폐기.
|
|
469
|
+
if (!jti)
|
|
470
|
+
throw new InvalidGrantError('refresh token 무효(구형) — 재로그인 필요');
|
|
471
|
+
const newJti = randomUUID();
|
|
472
|
+
const state = await consumeRefreshToken(jti, email, newJti);
|
|
473
|
+
if (state !== 'ok')
|
|
474
|
+
throw new InvalidGrantError('refresh token 무효(폐기됨) — 재로그인 필요');
|
|
475
|
+
const scope = (scopes && scopes.length ? scopes : grantedScopes).join(' ') || SCOPE;
|
|
476
|
+
const claims = { sub: email, email, scope, client_id: client.client_id };
|
|
477
|
+
const access_token = await signJwt(claims, ACCESS_TTL, 'access');
|
|
478
|
+
const refresh_token = await signJwt({ ...claims, jti: newJti }, REFRESH_TTL, 'refresh'); // rotation
|
|
479
|
+
await recordRefreshToken(newJti, email, client.client_id);
|
|
480
|
+
return { access_token, token_type: 'Bearer', expires_in: ACCESS_TTL, scope, refresh_token };
|
|
481
|
+
}
|
|
482
|
+
/** RS: access token 검증 (requireBearerAuth + 우리 /mcp dual-auth 가 사용) */
|
|
483
|
+
async verifyAccessToken(token) {
|
|
484
|
+
const { payload } = await jwtVerify(token, jwtKey(), {
|
|
485
|
+
issuer: ISSUER,
|
|
486
|
+
audience: RESOURCE,
|
|
487
|
+
algorithms: ['HS256'], // alg 고정 (none/RS256 confusion 차단)
|
|
488
|
+
});
|
|
489
|
+
if (payload.typ === 'refresh')
|
|
490
|
+
throw new Error('refresh token 으로 접근 불가');
|
|
491
|
+
const email = typeof payload.sub === 'string' ? payload.sub : '';
|
|
492
|
+
// P1: 매 요청 allowlist 재확인 — allowlist 에서 빠지면 미만료 토큰도 즉시 거부.
|
|
493
|
+
if (!emailAllowed(email))
|
|
494
|
+
throw new Error('계정이 더 이상 허용되지 않음');
|
|
495
|
+
const scopes = String(payload.scope || '').split(' ').filter(Boolean);
|
|
496
|
+
return {
|
|
497
|
+
token,
|
|
498
|
+
clientId: String(payload.client_id || ''),
|
|
499
|
+
scopes,
|
|
500
|
+
expiresAt: typeof payload.exp === 'number' ? payload.exp : undefined,
|
|
501
|
+
resource: new URL(RESOURCE),
|
|
502
|
+
extra: { email },
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
export const oauthProvider = new KnowledgeOAuthProvider();
|
|
507
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/core/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function appRoot(): string;
|
|
2
|
+
/** Candidate paths for a file: working directory first, then the app root. Duplicates removed. */
|
|
3
|
+
export declare function candidates(file: string): string[];
|
|
4
|
+
/** Data directory (SQLite store, checkpoints). MCP_DATA_DIR, else <app root>/data. */
|
|
5
|
+
export declare function dataDir(): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the server looks for .env, mcp.config.json and its data directory.
|
|
3
|
+
*
|
|
4
|
+
* MCP clients often spawn stdio servers from a different working directory, so the current directory
|
|
5
|
+
* alone is not enough. The "app root" is the nearest folder with a package.json above the entry script
|
|
6
|
+
* (your project when you run `node dist/server.js`), unless that is inside node_modules (npx), in which
|
|
7
|
+
* case the working directory is used.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
10
|
+
import { dirname, join, resolve } from 'node:path';
|
|
11
|
+
let _appRoot;
|
|
12
|
+
export function appRoot() {
|
|
13
|
+
if (_appRoot)
|
|
14
|
+
return _appRoot;
|
|
15
|
+
_appRoot = process.cwd();
|
|
16
|
+
const entry = process.argv[1];
|
|
17
|
+
if (entry) {
|
|
18
|
+
try {
|
|
19
|
+
let dir = dirname(realpathSync(entry));
|
|
20
|
+
for (let i = 0; i < 8; i++) {
|
|
21
|
+
if (existsSync(join(dir, 'package.json'))) {
|
|
22
|
+
if (!dir.split(/[\\/]/).includes('node_modules'))
|
|
23
|
+
_appRoot = dir;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
const up = dirname(dir);
|
|
27
|
+
if (up === dir)
|
|
28
|
+
break;
|
|
29
|
+
dir = up;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* entry not on disk (REPL, eval) → working directory */
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return _appRoot;
|
|
37
|
+
}
|
|
38
|
+
/** Candidate paths for a file: working directory first, then the app root. Duplicates removed. */
|
|
39
|
+
export function candidates(file) {
|
|
40
|
+
const list = [resolve(process.cwd(), file), resolve(appRoot(), file)];
|
|
41
|
+
return [...new Set(list)];
|
|
42
|
+
}
|
|
43
|
+
/** Data directory (SQLite store, checkpoints). MCP_DATA_DIR, else <app root>/data. */
|
|
44
|
+
export function dataDir() {
|
|
45
|
+
return process.env.MCP_DATA_DIR ? resolve(process.env.MCP_DATA_DIR) : join(appRoot(), 'data');
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/core/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,IAAI,QAA4B,CAAC;AAEjC,MAAM,UAAU,OAAO;IACrB,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,CAAC;YACH,IAAI,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;YACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3B,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;oBAC1C,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;wBAAE,QAAQ,GAAG,GAAG,CAAC;oBACjE,MAAM;gBACR,CAAC;gBACD,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;gBACxB,IAAI,EAAE,KAAK,GAAG;oBAAE,MAAM;gBACtB,GAAG,GAAG,EAAE,CAAC;YACX,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,wDAAwD;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,OAAO;IACrB,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AAChG,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 직원별 권한 판정 — 도구군(= 플러그인 id)과 AuthContext.scopes(app_users allowed_*)로 도구·채널·폴더를 거른다.
|
|
3
|
+
*
|
|
4
|
+
* 원칙:
|
|
5
|
+
* - admin 은 모든 scope 무시(전체 허용) — 운영 잠금 방지.
|
|
6
|
+
* - member(app_users 에 등록된 사용자): 각 차원 scope 가 null/undefined/빈배열이면 그 차원은 전체 허용
|
|
7
|
+
* (미설정 = 점진 도입). 명시 배열이 있으면 그 안의 것만 허용.
|
|
8
|
+
* - viewer(app_users 에 없음, 또는 조회 실패): 기본 거부. config access.viewerTools 에 적은 도구군만 허용.
|
|
9
|
+
* 로그인만 통과한 계정이 Drive·Slack·로그 전체를 읽는 것을 막는다.
|
|
10
|
+
* - 도구군이 없는 도구는 ping 만 허용한다. 플러그인에 속하지 않는 도구가 생겨도 기본 거부.
|
|
11
|
+
* Drive 제외 폴더(PII·계약·HR)는 이 scope 와 별개로 항상 적용된다.
|
|
12
|
+
*/
|
|
13
|
+
import type { AuthContext } from './auth.js';
|
|
14
|
+
/**
|
|
15
|
+
* 이 도구를 노출·호출해도 되는가. group = 도구가 속한 플러그인 id(registry.groupOf), 없으면 null.
|
|
16
|
+
* admin = 전체, member = allowed_tools(미설정=전체), viewer = config access.viewerTools 로 연 것만.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isToolAllowed(auth: AuthContext, name: string, group: string | null): boolean;
|
|
19
|
+
/** 허용 Slack 채널 id Set. null = 전체 허용(admin·미설정). */
|
|
20
|
+
export declare function allowedSlackChannels(auth: AuthContext): Set<string> | null;
|
|
21
|
+
/** 허용 Drive 폴더 path prefix 배열. null = 전체 허용(admin·미설정). */
|
|
22
|
+
export declare function allowedDriveFolders(auth: AuthContext): string[] | null;
|
|
23
|
+
/**
|
|
24
|
+
* path 가 허용 폴더 prefix 하위인가. prefixes null = 전체 허용.
|
|
25
|
+
* path 예: "개발문서/backend/..." · "01_고객사/Acme/제안서.pptx".
|
|
26
|
+
* prefix 예: "01_고객사" → 그 하위 전부 허용. 정확 일치 또는 "prefix/" 로 시작.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isPathInScope(path: string, prefixes: string[] | null): boolean;
|