@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.
Files changed (61) hide show
  1. package/README.md +2 -2
  2. package/config/dev-allowlist.json +28 -4
  3. package/dist/index.js +45 -0
  4. package/dist/oauth/dcr.d.ts +11 -0
  5. package/dist/oauth/dcr.js +49 -0
  6. package/dist/oauth/discovery.d.ts +11 -0
  7. package/dist/oauth/discovery.js +40 -0
  8. package/dist/oauth/pkce.d.ts +18 -0
  9. package/dist/oauth/pkce.js +34 -0
  10. package/dist/oauth/rate-limit.d.ts +20 -0
  11. package/dist/oauth/rate-limit.js +65 -0
  12. package/dist/oauth/store.d.ts +68 -0
  13. package/dist/oauth/store.js +247 -0
  14. package/dist/oauth/supabase-jwt.d.ts +39 -0
  15. package/dist/oauth/supabase-jwt.js +194 -0
  16. package/dist/oauth/token.d.ts +21 -0
  17. package/dist/oauth/token.js +213 -0
  18. package/dist/prompts.js +171 -159
  19. package/dist/server-sse.js +134 -36
  20. package/dist/tools/apps/bazi-app.d.ts +23 -0
  21. package/dist/tools/apps/bazi-app.js +224 -0
  22. package/dist/tools/apps/bi-wheel-app.d.ts +14 -7
  23. package/dist/tools/apps/bi-wheel-app.js +293 -108
  24. package/dist/tools/apps/bodygraph-app.js +38 -3
  25. package/dist/tools/apps/moon-phase-app.d.ts +19 -0
  26. package/dist/tools/apps/moon-phase-app.js +190 -0
  27. package/dist/tools/apps/transit-timeline-app.d.ts +20 -0
  28. package/dist/tools/apps/transit-timeline-app.js +243 -0
  29. package/dist/tools/apps/vedic-chart-app.d.ts +17 -0
  30. package/dist/tools/apps/vedic-chart-app.js +226 -0
  31. package/dist/tools/index.js +4 -0
  32. package/dist/tools/specialized/acg.js +2 -2
  33. package/dist/tools/specialized/bazi.js +367 -44
  34. package/dist/tools/specialized/bi_wheel.js +1 -1
  35. package/dist/tools/specialized/chart_wheel.js +1 -1
  36. package/dist/tools/specialized/comparative.js +4 -4
  37. package/dist/tools/specialized/eclipse.js +1 -1
  38. package/dist/tools/specialized/electional.js +4 -4
  39. package/dist/tools/specialized/ephemeris_core.js +2 -2
  40. package/dist/tools/specialized/ephemeris_extended.js +8 -8
  41. package/dist/tools/specialized/hd_bodygraph.js +1 -1
  42. package/dist/tools/specialized/hd_cycles.js +2 -2
  43. package/dist/tools/specialized/hd_group.js +2 -2
  44. package/dist/tools/specialized/human_design.js +1 -1
  45. package/dist/tools/specialized/moon.js +2 -2
  46. package/dist/tools/specialized/natal.js +1 -1
  47. package/dist/tools/specialized/progressed.js +1 -1
  48. package/dist/tools/specialized/relocation.js +1 -1
  49. package/dist/tools/specialized/returns.js +3 -3
  50. package/dist/tools/specialized/synastry.js +1 -1
  51. package/dist/tools/specialized/transits.js +1 -1
  52. package/dist/tools/specialized/vedic.js +1 -1
  53. package/dist/tools/specialized/venus_star_points.js +6 -6
  54. package/dist/ui/bazi.html +213 -0
  55. package/dist/ui/bi-wheel.html +762 -489
  56. package/dist/ui/bodygraph.html +1788 -1649
  57. package/dist/ui/chart-wheel.html +1772 -1593
  58. package/dist/ui/moon-phase.html +6758 -0
  59. package/dist/ui/transit-timeline.html +6835 -0
  60. package/dist/ui/vedic-chart.html +210 -0
  61. 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
+ }