@quadient/skillshare 1.6.0 → 1.6.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/README.md +10 -0
- package/dist/pages-dev-proxy.js +25 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -276,3 +276,13 @@ This is full-page reload, not React HMR. A CSP that disallows same-origin script
|
|
|
276
276
|
or connections must be adjusted for local development or the page reloaded manually.
|
|
277
277
|
The `/__skillshare_dev/` path is reserved for local development controls. This command
|
|
278
278
|
does not implement the SkillShare catalog, actual SSO or cloud deployment.
|
|
279
|
+
|
|
280
|
+
### Worker user directory (1.6.1)
|
|
281
|
+
|
|
282
|
+
Authenticated requests in `pages dev` include `X-Skillshare-Users-Url` and
|
|
283
|
+
`X-Skillshare-Users-Token`. A Worker can fetch the URL with the token in the
|
|
284
|
+
`X-Skillshare-Users-Token` request header to read the four local users. Each entry
|
|
285
|
+
contains `id`, `displayName`, `email`, `isAdmin` and `isOwner`. The hosted backend
|
|
286
|
+
uses the same contract for active SkillShare users. These capabilities stay in
|
|
287
|
+
the Worker and are not injected on `api/bypass-sso/*` requests. See the
|
|
288
|
+
[Worker example](../docs/pages-backends.md#skillshare-user-directory-from-a-worker).
|
package/dist/pages-dev-proxy.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServer, request as httpRequest, } from 'node:http';
|
|
2
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
3
|
const sessionCookie = 'skillshare.dev-user';
|
|
3
4
|
const internal = '/__skillshare_dev';
|
|
4
5
|
const hostCookie = (name) => name.startsWith('skillshare.auth') ||
|
|
@@ -25,7 +26,7 @@ function cleanHeaders(headers) {
|
|
|
25
26
|
delete result[name];
|
|
26
27
|
return result;
|
|
27
28
|
}
|
|
28
|
-
export function requestHeaders(request, user, baseUrl, slug) {
|
|
29
|
+
export function requestHeaders(request, user, baseUrl, slug, usersToken) {
|
|
29
30
|
const headers = cleanHeaders(request.headers);
|
|
30
31
|
for (const name of Object.keys(headers))
|
|
31
32
|
if (name.startsWith('x-skillshare-') ||
|
|
@@ -37,6 +38,10 @@ export function requestHeaders(request, user, baseUrl, slug) {
|
|
|
37
38
|
headers['accept-encoding'] = 'identity';
|
|
38
39
|
if (user) {
|
|
39
40
|
delete headers.authorization;
|
|
41
|
+
if (usersToken) {
|
|
42
|
+
headers['x-skillshare-users-url'] = new URL(`/api/pages/${encodeURIComponent(slug)}/users-directory`, baseUrl).href;
|
|
43
|
+
headers['x-skillshare-users-token'] = usersToken;
|
|
44
|
+
}
|
|
40
45
|
headers['x-skillshare-user'] = Buffer.from(JSON.stringify(user)).toString('base64');
|
|
41
46
|
const cookies = (request.headers.cookie ?? '')
|
|
42
47
|
.split(';')
|
|
@@ -74,6 +79,7 @@ export function responseCookies(cookies, prefix) {
|
|
|
74
79
|
}
|
|
75
80
|
export function createDevProxy(options) {
|
|
76
81
|
const prefix = `/p/${options.slug}`;
|
|
82
|
+
const usersToken = randomBytes(32).toString('hex');
|
|
77
83
|
const streams = new Set();
|
|
78
84
|
const upgrades = new Set();
|
|
79
85
|
const userFor = (request) => {
|
|
@@ -98,6 +104,22 @@ export function createDevProxy(options) {
|
|
|
98
104
|
return;
|
|
99
105
|
}
|
|
100
106
|
const url = new URL(request.url, `http://${request.headers.host}`);
|
|
107
|
+
if (url.pathname === `/api/pages/${encodeURIComponent(options.slug)}/users-directory`) {
|
|
108
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
109
|
+
const token = request.headers['x-skillshare-users-token'];
|
|
110
|
+
if (typeof token !== 'string' || !/^[a-f0-9]{64}$/.test(token) ||
|
|
111
|
+
!timingSafeEqual(Buffer.from(token), Buffer.from(usersToken))) {
|
|
112
|
+
response.writeHead(401).end();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (request.method !== 'GET') {
|
|
116
|
+
response.writeHead(405).end();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
120
|
+
response.end(JSON.stringify(options.users.map(({ id, displayName, email, isAdmin, isOwner }) => ({ id, displayName, email, isAdmin, isOwner }))));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
101
123
|
if (url.pathname === '/' || url.pathname === prefix) {
|
|
102
124
|
response.writeHead(307, { Location: prefix + '/' + url.search }).end();
|
|
103
125
|
return;
|
|
@@ -188,7 +210,7 @@ label.append(select); bar.append(label); document.body.append(bar);
|
|
|
188
210
|
const user = bypass ? undefined : userFor(request);
|
|
189
211
|
const upstream = httpRequest(new URL(path + url.search, target), {
|
|
190
212
|
method: request.method,
|
|
191
|
-
headers: requestHeaders(request, user, url.origin + prefix + '/', options.slug),
|
|
213
|
+
headers: requestHeaders(request, user, url.origin + prefix + '/', options.slug, usersToken),
|
|
192
214
|
}, (incoming) => {
|
|
193
215
|
const headers = cleanHeaders(incoming.headers);
|
|
194
216
|
headers['cache-control'] = 'private, no-cache';
|
|
@@ -251,7 +273,7 @@ label.append(select); bar.append(label); document.body.append(bar);
|
|
|
251
273
|
return;
|
|
252
274
|
}
|
|
253
275
|
const bypass = path === '/api/bypass-sso' || path.startsWith('/api/bypass-sso/');
|
|
254
|
-
const headers = requestHeaders(request, bypass ? undefined : userFor(request), url.origin + prefix + '/', options.slug);
|
|
276
|
+
const headers = requestHeaders(request, bypass ? undefined : userFor(request), url.origin + prefix + '/', options.slug, usersToken);
|
|
255
277
|
headers.connection = 'Upgrade';
|
|
256
278
|
headers.upgrade = request.headers.upgrade;
|
|
257
279
|
const upstream = httpRequest(new URL(path + url.search, target), {
|