@deepseek-ai/dsh-deepseek-account-platform 0.1.7-alpha.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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +96 -0
- package/README.zh.md +96 -0
- package/lib/index.js +891 -0
- package/lib/types/details.d.ts +17 -0
- package/lib/types/details.js +55 -0
- package/lib/types/index.d.ts +132 -0
- package/lib/types/index.js +488 -0
- package/lib/types/logout.d.ts +17 -0
- package/lib/types/logout.js +34 -0
- package/lib/types/protocol.d.ts +78 -0
- package/lib/types/protocol.js +210 -0
- package/lib/types/types.d.ts +3 -0
- package/lib/types/types.js +2 -0
- package/package.json +52 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/** Validated platform HTTP messages and restricted browser destinations. */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/** Protocol errors expose a stable code, never a response body or authorization URL. */
|
|
4
|
+
export class PlatformAuthError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
/** @param code - safe error classification. */
|
|
7
|
+
constructor(code) {
|
|
8
|
+
super(`account: ${code}`);
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Accept HTTPS platform endpoints, or explicitly configured loopback development HTTP.
|
|
14
|
+
* @param value - configured origin.
|
|
15
|
+
* @param allowLoopbackHttp - development-only opt-in.
|
|
16
|
+
* @returns normalized origin.
|
|
17
|
+
*/
|
|
18
|
+
export function platformOrigin(value, allowLoopbackHttp) {
|
|
19
|
+
const url = new URL(value);
|
|
20
|
+
const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
|
|
21
|
+
if (url.username || url.password || url.pathname !== '/' || url.search || url.hash
|
|
22
|
+
|| !(url.protocol === 'https:' || (allowLoopbackHttp && loopback && url.protocol === 'http:'))) {
|
|
23
|
+
throw new Error('account: platformOrigin must be an HTTPS origin or explicitly enabled loopback HTTP origin');
|
|
24
|
+
}
|
|
25
|
+
return url.origin;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validate platform-owned browser destinations without forwarding arbitrary URLs.
|
|
29
|
+
* @param value - returned browser URL.
|
|
30
|
+
* @param origin - configured platform origin.
|
|
31
|
+
* @param path - fixed authorize or completion path.
|
|
32
|
+
* @param rewriteOrigin - map validated browser pages to the configured development origin.
|
|
33
|
+
* @returns normalized URL on the configured origin.
|
|
34
|
+
*/
|
|
35
|
+
export function browserUrl(value, origin, path, rewriteOrigin = false) {
|
|
36
|
+
let url;
|
|
37
|
+
try {
|
|
38
|
+
url = new URL(value);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
console.info('[deepseek-account] browser URL rejected', { path, reason: 'invalid-url' });
|
|
42
|
+
throw new PlatformAuthError('protocol');
|
|
43
|
+
}
|
|
44
|
+
const allowedOrigin = url.origin === origin || (rewriteOrigin && url.protocol === 'https:');
|
|
45
|
+
if (!allowedOrigin || url.pathname !== path || url.username || url.password || url.hash) {
|
|
46
|
+
console.info('[deepseek-account] browser URL rejected', {
|
|
47
|
+
path, originMismatch: !allowedOrigin, pathMismatch: url.pathname !== path,
|
|
48
|
+
hasCredentials: Boolean(url.username || url.password), hasFragment: Boolean(url.hash),
|
|
49
|
+
});
|
|
50
|
+
throw new PlatformAuthError('protocol');
|
|
51
|
+
}
|
|
52
|
+
return rewriteOrigin ? `${origin}${url.pathname}${url.search}` : url.href;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate Host-only deployment headers without exposing their values in diagnostics.
|
|
56
|
+
* @param values - configured headers for the Platform origin.
|
|
57
|
+
* @returns normalized headers; authorization, routing and framing remain provider-owned.
|
|
58
|
+
*/
|
|
59
|
+
export function platformHeaders(values) {
|
|
60
|
+
const headers = new Headers();
|
|
61
|
+
const names = new Set();
|
|
62
|
+
for (const [name, value] of Object.entries(values)) {
|
|
63
|
+
const key = name.toLowerCase();
|
|
64
|
+
if (['authorization', 'x-dsh-auth-token', 'host', 'content-length', 'transfer-encoding', 'connection', 'content-type'].includes(key)
|
|
65
|
+
|| names.has(key))
|
|
66
|
+
throw new Error('account: requestHeaders contains a reserved or duplicate header');
|
|
67
|
+
names.add(key);
|
|
68
|
+
try {
|
|
69
|
+
headers.set(name, value);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw new Error('account: requestHeaders contains an invalid header');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return Object.fromEntries(headers);
|
|
76
|
+
}
|
|
77
|
+
const envelope = z.object({ code: z.literal(0), data: z.object({ biz_code: z.number().int(), biz_data: z.unknown() }) });
|
|
78
|
+
/** Successful initialization response. */
|
|
79
|
+
export const initialization = z.object({
|
|
80
|
+
authorize_url: z.url(), authorize_id: z.string().min(1), expires_in: z.number().positive(),
|
|
81
|
+
});
|
|
82
|
+
/** Successful code exchange response. */
|
|
83
|
+
export const exchange = z.object({ token: z.string().regex(/^[\x21-\x7e]+$/), authorized_url: z.url(), user: z.unknown().optional() });
|
|
84
|
+
/**
|
|
85
|
+
* Read one bounded platform response with stable, non-secret diagnostics.
|
|
86
|
+
* @param origin - validated platform origin.
|
|
87
|
+
* @param method - platform endpoint suffix.
|
|
88
|
+
* @param body - protocol request, never logged.
|
|
89
|
+
* @param signal - attempt cancellation and timeout.
|
|
90
|
+
* @param headers - validated deployment headers for this origin.
|
|
91
|
+
* @returns successful business payload, validated by its caller.
|
|
92
|
+
*/
|
|
93
|
+
export async function requestPlatform(origin, method, body, signal, headers) {
|
|
94
|
+
return platformRequest(`${origin}/auth-api/v0/dsh/${method}`, {
|
|
95
|
+
method: 'POST', headers: { ...headers, 'content-type': 'application/json' }, body: JSON.stringify(body),
|
|
96
|
+
}, signal);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Fetch a fixed Platform account endpoint with the grant kept in Host request headers.
|
|
100
|
+
* @param origin - configured and grant-matched origin.
|
|
101
|
+
* @param path - account endpoint.
|
|
102
|
+
* @param token - account grant.
|
|
103
|
+
* @param signal - credential lifetime and request timeout.
|
|
104
|
+
* @param headers - validated deployment headers for this origin.
|
|
105
|
+
* @returns successful business payload.
|
|
106
|
+
*/
|
|
107
|
+
export function requestAccount(origin, path, token, signal, headers) {
|
|
108
|
+
return platformRequest(`${origin}${path}`, { method: 'GET', headers: { ...headers, 'x-dsh-auth-token': token } }, signal);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* End the Platform session using its existing logout endpoint.
|
|
112
|
+
* @param origin - configured origin matching the grant issuer.
|
|
113
|
+
* @param token - stored account token.
|
|
114
|
+
* @param signal - logout request deadline.
|
|
115
|
+
* @param headers - validated deployment headers for this origin.
|
|
116
|
+
* @returns after Platform confirms logout.
|
|
117
|
+
*/
|
|
118
|
+
export async function logoutAccount(origin, token, signal, headers) {
|
|
119
|
+
await platformRequest(`${origin}/auth-api/v0/users/logout`, {
|
|
120
|
+
method: 'POST', headers: { ...headers, 'x-dsh-auth-token': token },
|
|
121
|
+
}, signal);
|
|
122
|
+
}
|
|
123
|
+
async function platformRequest(url, init, signal) {
|
|
124
|
+
const path = new URL(url).pathname;
|
|
125
|
+
console.info('[deepseek-account] request', { path, method: init.method });
|
|
126
|
+
let response;
|
|
127
|
+
try {
|
|
128
|
+
response = await fetch(url, { ...init, redirect: 'error', signal });
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
console.info('[deepseek-account] request failed', { path, errorCode: 'network', aborted: signal.aborted });
|
|
132
|
+
throw new PlatformAuthError('network');
|
|
133
|
+
}
|
|
134
|
+
console.info('[deepseek-account] response', { path, status: response.status });
|
|
135
|
+
if (!response.ok || response.body === null) {
|
|
136
|
+
await response.body?.cancel();
|
|
137
|
+
throw new PlatformAuthError('network');
|
|
138
|
+
}
|
|
139
|
+
const reader = response.body.getReader();
|
|
140
|
+
const chunks = [];
|
|
141
|
+
let size = 0;
|
|
142
|
+
let stage = 'read-body';
|
|
143
|
+
try {
|
|
144
|
+
while (true) {
|
|
145
|
+
const next = await reader.read();
|
|
146
|
+
if (next.done)
|
|
147
|
+
break;
|
|
148
|
+
size += next.value.byteLength;
|
|
149
|
+
if (size > 65_536) {
|
|
150
|
+
stage = 'body-limit';
|
|
151
|
+
throw new PlatformAuthError('protocol');
|
|
152
|
+
}
|
|
153
|
+
chunks.push(next.value);
|
|
154
|
+
}
|
|
155
|
+
stage = 'parse-json';
|
|
156
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
157
|
+
const codes = z.object({ code: z.number().int(), data: z.object({ biz_code: z.number().int() }).optional() }).safeParse(payload);
|
|
158
|
+
if (codes.success)
|
|
159
|
+
console.info('[deepseek-account] response codes', {
|
|
160
|
+
path, code: codes.data.code, bizCode: codes.data.data?.biz_code,
|
|
161
|
+
});
|
|
162
|
+
stage = 'envelope';
|
|
163
|
+
const parsed = envelope.safeParse(payload);
|
|
164
|
+
if (!parsed.success) {
|
|
165
|
+
console.info('[deepseek-account] envelope rejected', {
|
|
166
|
+
path, issues: parsed.error.issues.map(issue => ({ path: issue.path, code: issue.code })),
|
|
167
|
+
});
|
|
168
|
+
throw new PlatformAuthError('protocol');
|
|
169
|
+
}
|
|
170
|
+
stage = 'business-code';
|
|
171
|
+
if (parsed.data.data.biz_code !== 0) {
|
|
172
|
+
// TODO(product-error-ui): Apply product-defined copy and UI behavior for the supplied biz_code values.
|
|
173
|
+
// Business failures use the existing generic failure UI until then; backend messages stay Host-only.
|
|
174
|
+
throw new PlatformAuthError('protocol');
|
|
175
|
+
}
|
|
176
|
+
return parsed.data.data.biz_data;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.info('[deepseek-account] response rejected', { path, stage, errorCode: 'protocol' });
|
|
180
|
+
if (error instanceof PlatformAuthError)
|
|
181
|
+
throw error;
|
|
182
|
+
throw new PlatformAuthError('protocol');
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
await reader.cancel().catch(() => undefined);
|
|
186
|
+
reader.releaseLock();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Accept a browser-accessible loopback HTTP origin for local or SSH-forwarded login.
|
|
191
|
+
* @param value - loopback HTTP origin with an explicit port supplied by the authenticated initiating client.
|
|
192
|
+
* @returns normalized origin; remote domains and path-based proxies are unsupported.
|
|
193
|
+
*/
|
|
194
|
+
export function loginOrigin(value) {
|
|
195
|
+
let url;
|
|
196
|
+
try {
|
|
197
|
+
url = new URL(value);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
throw new PlatformAuthError('protocol');
|
|
201
|
+
}
|
|
202
|
+
const explicitPort = /^http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):([0-9]+)\/?$/i.exec(value)?.[1];
|
|
203
|
+
if (explicitPort === undefined || Number(explicitPort) === 0
|
|
204
|
+
|| url.protocol !== 'http:' || !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)
|
|
205
|
+
|| url.username || url.password || url.pathname !== '/' || url.search || url.hash) {
|
|
206
|
+
throw new PlatformAuthError('protocol');
|
|
207
|
+
}
|
|
208
|
+
return `${url.protocol}//${url.hostname}:${Number(explicitPort)}`;
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=protocol.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-deepseek-account-platform",
|
|
3
|
+
"description": "Authorize DeepSeek accounts through browser PKCE",
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/credentials/deepseek-account-platform"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./types": {
|
|
22
|
+
"types": "./lib/types/types.d.ts",
|
|
23
|
+
"default": "./lib/types/types.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/types/**/*.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
36
|
+
"@deepseek-ai/dsh-deepseek-account": "^0.1.7-alpha.1",
|
|
37
|
+
"@deepseek-ai/dsh-authorization": "^0.1.7-alpha.1",
|
|
38
|
+
"@deepseek-ai/dsh-credentials": "^0.1.7-alpha.1",
|
|
39
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-alpha.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
43
|
+
"@deepseek-ai/dsh-deepseek-account": "^0.1.7-alpha.1",
|
|
44
|
+
"@deepseek-ai/dsh-authorization": "^0.1.7-alpha.1",
|
|
45
|
+
"@deepseek-ai/dsh-credentials": "^0.1.7-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-alpha.1"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"zod": "^4.4.3",
|
|
50
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
51
|
+
}
|
|
52
|
+
}
|