@openephemeris/mcp-server 3.13.9 → 3.13.10
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 +2 -2
- package/config/dev-allowlist.json +28 -4
- package/dist/index.js +45 -0
- package/dist/oauth/dcr.d.ts +11 -0
- package/dist/oauth/dcr.js +49 -0
- package/dist/oauth/discovery.d.ts +11 -0
- package/dist/oauth/discovery.js +40 -0
- package/dist/oauth/pkce.d.ts +18 -0
- package/dist/oauth/pkce.js +34 -0
- package/dist/oauth/rate-limit.d.ts +20 -0
- package/dist/oauth/rate-limit.js +65 -0
- package/dist/oauth/store.d.ts +68 -0
- package/dist/oauth/store.js +247 -0
- package/dist/oauth/supabase-jwt.d.ts +39 -0
- package/dist/oauth/supabase-jwt.js +194 -0
- package/dist/oauth/token.d.ts +21 -0
- package/dist/oauth/token.js +213 -0
- package/dist/prompts.js +171 -159
- package/dist/server-sse.js +134 -36
- package/dist/tools/apps/bazi-app.d.ts +23 -0
- package/dist/tools/apps/bazi-app.js +224 -0
- package/dist/tools/apps/bi-wheel-app.d.ts +14 -7
- package/dist/tools/apps/bi-wheel-app.js +293 -108
- package/dist/tools/apps/bodygraph-app.js +38 -3
- package/dist/tools/apps/moon-phase-app.d.ts +19 -0
- package/dist/tools/apps/moon-phase-app.js +190 -0
- package/dist/tools/apps/transit-timeline-app.d.ts +20 -0
- package/dist/tools/apps/transit-timeline-app.js +243 -0
- package/dist/tools/apps/vedic-chart-app.d.ts +17 -0
- package/dist/tools/apps/vedic-chart-app.js +226 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/specialized/acg.js +2 -2
- package/dist/tools/specialized/bazi.js +367 -44
- package/dist/tools/specialized/bi_wheel.js +1 -1
- package/dist/tools/specialized/chart_wheel.js +1 -1
- package/dist/tools/specialized/comparative.js +4 -4
- package/dist/tools/specialized/eclipse.js +1 -1
- package/dist/tools/specialized/electional.js +4 -4
- package/dist/tools/specialized/ephemeris_core.js +2 -2
- package/dist/tools/specialized/ephemeris_extended.js +8 -8
- package/dist/tools/specialized/hd_bodygraph.js +1 -1
- package/dist/tools/specialized/hd_cycles.js +2 -2
- package/dist/tools/specialized/hd_group.js +2 -2
- package/dist/tools/specialized/human_design.js +1 -1
- package/dist/tools/specialized/moon.js +2 -2
- package/dist/tools/specialized/natal.js +1 -1
- package/dist/tools/specialized/progressed.js +1 -1
- package/dist/tools/specialized/relocation.js +1 -1
- package/dist/tools/specialized/returns.js +3 -3
- package/dist/tools/specialized/synastry.js +1 -1
- package/dist/tools/specialized/transits.js +1 -1
- package/dist/tools/specialized/vedic.js +1 -1
- package/dist/tools/specialized/venus_star_points.js +6 -6
- package/dist/ui/bazi.html +213 -0
- package/dist/ui/bi-wheel.html +762 -489
- package/dist/ui/bodygraph.html +1788 -1649
- package/dist/ui/chart-wheel.html +1772 -1593
- package/dist/ui/moon-phase.html +6758 -0
- package/dist/ui/transit-timeline.html +6835 -0
- package/dist/ui/vedic-chart.html +210 -0
- package/package.json +2 -2
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/oauth/token.ts — OAuth 2.1 Token endpoint.
|
|
3
|
+
*
|
|
4
|
+
* POST /oauth/token
|
|
5
|
+
*
|
|
6
|
+
* Supports:
|
|
7
|
+
* - grant_type=authorization_code (with PKCE S256 verification)
|
|
8
|
+
* - grant_type=refresh_token (with rotation enforcement)
|
|
9
|
+
*
|
|
10
|
+
* Token issuance strategy:
|
|
11
|
+
* 1. In-memory store validates code/PKCE/redirect_uri
|
|
12
|
+
* 2. If Supabase env vars are set AND the stored code has a user_id,
|
|
13
|
+
* issue a real Supabase JWT via supabase-jwt.ts
|
|
14
|
+
* 3. Otherwise fall back to local placeholder tokens (test path)
|
|
15
|
+
*
|
|
16
|
+
* The Supabase path is never attempted unless BOTH SUPABASE_URL and
|
|
17
|
+
* SUPABASE_SERVICE_ROLE_KEY are set. Tests run without either.
|
|
18
|
+
*/
|
|
19
|
+
import express from "express";
|
|
20
|
+
import { timingSafeEqual } from "node:crypto";
|
|
21
|
+
import { verifyCodeChallenge } from "./pkce.js";
|
|
22
|
+
import { issueSupabaseJWT, refreshSupabaseSession } from "./supabase-jwt.js";
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* Timing-safe string comparison for redirect URI validation.
|
|
28
|
+
*/
|
|
29
|
+
function timingSafeStringEqual(a, b) {
|
|
30
|
+
const bufA = Buffer.from(a, "utf8");
|
|
31
|
+
const bufB = Buffer.from(b, "utf8");
|
|
32
|
+
if (bufA.length !== bufB.length)
|
|
33
|
+
return false;
|
|
34
|
+
return timingSafeEqual(bufA, bufB);
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Token Router Factory
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
export function oauthTokenRouter(store) {
|
|
40
|
+
const router = express.Router();
|
|
41
|
+
router.post("/oauth/token", async (req, res) => {
|
|
42
|
+
const grant_type = req.body.grant_type;
|
|
43
|
+
if (grant_type === "authorization_code") {
|
|
44
|
+
await handleAuthorizationCode(req, res, store);
|
|
45
|
+
}
|
|
46
|
+
else if (grant_type === "refresh_token") {
|
|
47
|
+
await handleRefreshToken(req, res, store);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
res.status(400).json({
|
|
51
|
+
error: "unsupported_grant_type",
|
|
52
|
+
error_description: `Expected "authorization_code" or "refresh_token".`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return router;
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Authorization Code Exchange
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
async function handleAuthorizationCode(req, res, store) {
|
|
62
|
+
const { code, code_verifier, redirect_uri, client_id } = req.body;
|
|
63
|
+
if (!code) {
|
|
64
|
+
res.status(400).json({
|
|
65
|
+
error: "invalid_request",
|
|
66
|
+
error_description: "Missing authorization code.",
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!code_verifier) {
|
|
71
|
+
res.status(400).json({
|
|
72
|
+
error: "invalid_request",
|
|
73
|
+
error_description: "Missing code_verifier (PKCE required).",
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Consume the authorization code (single-use).
|
|
78
|
+
// consumeCodeAsync works with both Supabase (production) and in-memory (tests).
|
|
79
|
+
const stored = await store.consumeCodeAsync(code);
|
|
80
|
+
if (!stored) {
|
|
81
|
+
res.status(400).json({
|
|
82
|
+
error: "invalid_grant",
|
|
83
|
+
error_description: "Authorization code is invalid, expired, or already used.",
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Verify PKCE challenge
|
|
88
|
+
if (!verifyCodeChallenge(code_verifier, stored.code_challenge)) {
|
|
89
|
+
res.status(400).json({
|
|
90
|
+
error: "invalid_grant",
|
|
91
|
+
error_description: "PKCE code_verifier does not match the stored code_challenge.",
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Verify redirect_uri exact match
|
|
96
|
+
if (redirect_uri && !timingSafeStringEqual(redirect_uri, stored.redirect_uri)) {
|
|
97
|
+
res.status(400).json({
|
|
98
|
+
error: "invalid_grant",
|
|
99
|
+
error_description: "redirect_uri does not match the value used during authorization.",
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Token issuance — prefer Supabase JWT, fall back to placeholder
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// If the authorization code was issued with a user_id (consent page stores it),
|
|
107
|
+
// attempt to issue a real Supabase JWT that the Go sidecar can validate.
|
|
108
|
+
if (stored.user_id) {
|
|
109
|
+
const supabaseTokens = await issueSupabaseJWT(stored.user_id);
|
|
110
|
+
if (supabaseTokens) {
|
|
111
|
+
// Store refresh token mapping for rotation enforcement
|
|
112
|
+
const oauthRefreshToken = store.generateRefreshToken();
|
|
113
|
+
store.storeRefreshToken(oauthRefreshToken, {
|
|
114
|
+
client_id: stored.client_id,
|
|
115
|
+
user_id: stored.user_id,
|
|
116
|
+
// Keep the Supabase refresh token for downstream refresh
|
|
117
|
+
supabase_refresh_token: supabaseTokens.refresh_token,
|
|
118
|
+
});
|
|
119
|
+
console.info(`[token] Supabase JWT issued for user ${stored.user_id}`);
|
|
120
|
+
res.json({
|
|
121
|
+
access_token: supabaseTokens.access_token,
|
|
122
|
+
refresh_token: oauthRefreshToken,
|
|
123
|
+
token_type: "Bearer",
|
|
124
|
+
expires_in: supabaseTokens.expires_in,
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Supabase JWT issuance failed — log a warning but continue to placeholder
|
|
129
|
+
console.warn(`[token] Supabase JWT issuance failed for user ${stored.user_id}, falling back to placeholder`);
|
|
130
|
+
}
|
|
131
|
+
// Fallback: local placeholder tokens (test/dev path)
|
|
132
|
+
const refreshToken = store.generateRefreshToken();
|
|
133
|
+
store.storeRefreshToken(refreshToken, { client_id: stored.client_id });
|
|
134
|
+
res.json({
|
|
135
|
+
access_token: `oe_access_${Date.now()}`,
|
|
136
|
+
refresh_token: refreshToken,
|
|
137
|
+
token_type: "Bearer",
|
|
138
|
+
expires_in: 3600,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Refresh Token Exchange
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
async function handleRefreshToken(req, res, store) {
|
|
145
|
+
const { refresh_token } = req.body;
|
|
146
|
+
if (!refresh_token) {
|
|
147
|
+
res.status(400).json({
|
|
148
|
+
error: "invalid_request",
|
|
149
|
+
error_description: "Missing refresh_token.",
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Consume from local store (rotation enforcement)
|
|
154
|
+
const storedMeta = store.consumeRefreshToken(refresh_token);
|
|
155
|
+
if (!storedMeta) {
|
|
156
|
+
res.status(400).json({
|
|
157
|
+
error: "invalid_grant",
|
|
158
|
+
error_description: "Invalid or expired refresh token.",
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Refresh — prefer Supabase, fall back to placeholder
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
const userId = storedMeta.user_id;
|
|
166
|
+
const supabaseRefresh = storedMeta.supabase_refresh_token;
|
|
167
|
+
// Try Supabase refresh first
|
|
168
|
+
if (supabaseRefresh) {
|
|
169
|
+
const refreshed = await refreshSupabaseSession(supabaseRefresh);
|
|
170
|
+
if (refreshed) {
|
|
171
|
+
// Issue rotated OAuth refresh token
|
|
172
|
+
const newRefreshToken = store.generateRefreshToken();
|
|
173
|
+
store.storeRefreshToken(newRefreshToken, {
|
|
174
|
+
...storedMeta,
|
|
175
|
+
supabase_refresh_token: refreshed.refresh_token,
|
|
176
|
+
});
|
|
177
|
+
res.json({
|
|
178
|
+
access_token: refreshed.access_token,
|
|
179
|
+
refresh_token: newRefreshToken,
|
|
180
|
+
token_type: "Bearer",
|
|
181
|
+
expires_in: refreshed.expires_in,
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// If Supabase refresh failed but we have a user_id, try issuing a fresh JWT
|
|
187
|
+
if (userId) {
|
|
188
|
+
const freshTokens = await issueSupabaseJWT(userId);
|
|
189
|
+
if (freshTokens) {
|
|
190
|
+
const newRefreshToken = store.generateRefreshToken();
|
|
191
|
+
store.storeRefreshToken(newRefreshToken, {
|
|
192
|
+
...storedMeta,
|
|
193
|
+
supabase_refresh_token: freshTokens.refresh_token,
|
|
194
|
+
});
|
|
195
|
+
res.json({
|
|
196
|
+
access_token: freshTokens.access_token,
|
|
197
|
+
refresh_token: newRefreshToken,
|
|
198
|
+
token_type: "Bearer",
|
|
199
|
+
expires_in: freshTokens.expires_in,
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Fallback: placeholder tokens
|
|
205
|
+
const newRefreshToken = store.generateRefreshToken();
|
|
206
|
+
store.storeRefreshToken(newRefreshToken, storedMeta);
|
|
207
|
+
res.json({
|
|
208
|
+
access_token: `oe_access_${Date.now()}`,
|
|
209
|
+
refresh_token: newRefreshToken,
|
|
210
|
+
token_type: "Bearer",
|
|
211
|
+
expires_in: 3600,
|
|
212
|
+
});
|
|
213
|
+
}
|