forked from decolua/9router
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroute.js
More file actions
166 lines (155 loc) · 5.6 KB
/
Copy pathroute.js
File metadata and controls
166 lines (155 loc) · 5.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import { NextResponse } from "next/server";
import { createProviderConnection } from "@/models";
import {
resolveRaycastSecrets,
decodeAidFromRaycastJwt,
fetchRaycastModels,
probeRaycastChat,
} from "open-sse/services/raycast.js";
/**
* POST /api/oauth/raycast/import
* Import and validate a Raycast Pro bearer token (+ deviceId + optional sig secret/JWT).
*
* Validation: besides listing models (which succeeds even for a garbage token), a
* real one-turn chat probe is issued so that cookie-style / unsigned-in tokens are
* rejected and never persisted as an active connection.
*/
export async function POST(request) {
try {
const body = await request.json().catch(() => ({}));
const accessToken = String(body.accessToken || "").trim();
const deviceId = String(body.deviceId || "").trim();
const sigSecret = String(body.sigSecret || body.sidSecret || "").trim();
const aidHint = String(body.aid || "").trim();
const signatureJwt = String(body.signatureJwt || body.sidSecret || "").trim();
if (!accessToken) {
return NextResponse.json({ error: "Access token is required" }, { status: 400 });
}
if (!deviceId) {
return NextResponse.json({ error: "Device ID (X-Raycast-DeviceId) is required" }, { status: 400 });
}
// Reject cookie-style captures (e.g. "csrf_token=...;") so a bogus import
// surfaces immediately instead of silently becoming an "active" connection.
const isJwt = /^[A-Za-z0-9\-_.]+\.[A-Za-z0-9\-_.]+\.[A-Za-z0-9\-_.]+$/.test(accessToken);
const cookieStyle = /^[a-zA-Z0-9_+.\-]+=/.test(accessToken);
if (cookieStyle && !accessToken.startsWith("rca_") && !isJwt) {
return NextResponse.json(
{
error:
"That looks like a cookie, not an OAuth bearer token. Capture the Authorization: Bearer <rca_...> value from backend.raycast.com during a Raycast chat — do not paste the csrf_token cookie.",
},
{ status: 400 }
);
}
// Resolve the account ID: explicit hint, then the signature JWT payload, then deviceId.
const aid = aidHint || decodeAidFromRaycastJwt(signatureJwt) || deviceId;
const providerSpecificData = {
deviceId,
aid,
...(sigSecret ? { sigSecret } : {}),
authMethod: "imported",
};
// Shape validation (throws if required fields missing).
const creds = { accessToken, providerSpecificData };
try {
resolveRaycastSecrets(creds);
} catch (err) {
return NextResponse.json({ error: err.message }, { status: 400 });
}
// Real auth gate — the chat probe is what actually validates the token.
let probe;
try {
probe = await probeRaycastChat(creds);
} catch (err) {
return NextResponse.json({ error: `Raycast probe failed: ${err.message}` }, { status: 502 });
}
if (!probe.ok) {
return NextResponse.json(
{ error: `Raycast validation failed: ${probe.error}` },
{ status: Number(probe.status) || 401 }
);
}
// Connectivity + premium model counts.
let models = [];
let modelCount = 0;
let premiumModelCount = 0;
try {
models = await fetchRaycastModels(creds);
modelCount = models.length;
premiumModelCount = models.filter((m) => m.requires_better_ai).length;
} catch {
// probe already succeeded; counts are informational only.
}
const connection = await createProviderConnection({
provider: "raycast",
authType: "oauth",
accessToken,
refreshToken: null,
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
providerSpecificData: {
deviceId,
aid,
...(sigSecret ? { sigSecret } : {}),
authMethod: "imported",
modelCount,
premiumModelCount,
},
testStatus: "active",
});
return NextResponse.json({
success: true,
connection: { id: connection.id, provider: "raycast" },
models: {
total: modelCount,
premium: premiumModelCount,
sample: models.slice(0, 8).map((m) => m.id),
},
});
} catch (error) {
console.log("Raycast import token error:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
/**
* GET /api/oauth/raycast/import
* Instructions for required Raycast fields.
*/
export async function GET() {
return NextResponse.json({
provider: "raycast",
method: "import_token",
instructions: [
"Easiest: click Auto-Import (macOS) — reads Keychain + local Raycast DB.",
"Manual fallback: Proxyman/Charles SSL proxy on backend.raycast.com.",
"Bearer token lives in Keychain: Raycast / raycast-store_credentials (it is the rca_... oauth.access_token).",
"Device ID = analyticsId in ~/Library/Application Support/com.raycast.macos/posthog.distinctId.",
"Signature JWT is optional with current Raycast builds.",
],
requiredFields: [
{
name: "accessToken",
label: "Bearer Token",
description: "From Authorization: Bearer header on backend.raycast.com requests (starts with rca_)",
type: "textarea",
},
{
name: "deviceId",
label: "Device ID",
description: "From X-Raycast-DeviceId header",
type: "text",
},
{
name: "signatureJwt",
label: "Signature JWT",
description: "From X-Raycast-Signature header (optional; AID decoded automatically)",
type: "textarea",
},
{
name: "sigSecret",
label: "Signature Secret",
description: "Optional override — defaults to community-extracted SIG_SECRET",
type: "text",
},
],
});
}