pockcode 0.0.1 → 0.0.4
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/build/client/assets/{index-DQn6eSCt.js → index-NZZ2Aqx9.js} +10 -10
- package/build/client/index.html +1 -1
- package/dist/assets/{api.server-D6ONM6ZJ.js → api.server-Dcqq4Zzw.js} +3 -3
- package/dist/assets/auth.server-ByhZCVHW.js +162 -0
- package/dist/assets/{chat-status-monitor.server-D2VG_D8P.js → chat-status-monitor.server-GIsx-97a.js} +1 -1
- package/dist/assets/{chats.service-H6m23Fna.js → chats.service-vjB782qi.js} +1 -1
- package/dist/assets/{manager.server-Cru4ZxHo.js → manager.server-C_G8l7U-.js} +1 -1
- package/dist/assets/{message-schedule-monitor.server-C4NZg62k.js → message-schedule-monitor.server-CAxiClld.js} +1 -1
- package/dist/assets/{message-schedules.service-Be9t4LDg.js → message-schedules.service-C5TTaPXz.js} +1 -1
- package/dist/pockcode.js +345 -39
- package/package.json +2 -2
- package/dist/assets/auth.server-B_P8Jm6O.js +0 -87
package/dist/pockcode.js
CHANGED
|
@@ -20,12 +20,12 @@ async function main() {
|
|
|
20
20
|
process.env.NODE_ENV ||= "production";
|
|
21
21
|
if (parsed.home) process.env.POCKCODE_HOME = parsed.home;
|
|
22
22
|
const [api, auth, socket, chatMonitor, scheduleMonitor, pluginManager] = await Promise.all([
|
|
23
|
-
import("./assets/api.server-
|
|
24
|
-
import("./assets/auth.server-
|
|
23
|
+
import("./assets/api.server-Dcqq4Zzw.js"),
|
|
24
|
+
import("./assets/auth.server-ByhZCVHW.js"),
|
|
25
25
|
import("./assets/socket.server-7dC-9Fnw.js").then((n) => n.a),
|
|
26
|
-
import("./assets/chat-status-monitor.server-
|
|
27
|
-
import("./assets/message-schedule-monitor.server-
|
|
28
|
-
import("./assets/manager.server-
|
|
26
|
+
import("./assets/chat-status-monitor.server-GIsx-97a.js"),
|
|
27
|
+
import("./assets/message-schedule-monitor.server-CAxiClld.js"),
|
|
28
|
+
import("./assets/manager.server-C_G8l7U-.js").then((n) => n.r)
|
|
29
29
|
]);
|
|
30
30
|
const clientRoot = await resolveClientRoot();
|
|
31
31
|
const server = createServer((req, res) => {
|
|
@@ -60,39 +60,96 @@ async function handleAuthGate(req, res, url, auth) {
|
|
|
60
60
|
return true;
|
|
61
61
|
}
|
|
62
62
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
63
|
-
sendSetupPage(res);
|
|
63
|
+
sendSetupPage(res, { returnTo: returnToFor(url) });
|
|
64
64
|
return true;
|
|
65
65
|
}
|
|
66
66
|
sendJson(res, 428, { error: "Pockcode password setup is required." });
|
|
67
67
|
return true;
|
|
68
68
|
}
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
if (url.pathname === "/auth/login" && req.method === "POST") {
|
|
70
|
+
await handlePasswordLogin(req, res, auth);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (url.pathname === "/auth/logout" && req.method === "POST") {
|
|
74
|
+
handlePasswordLogout(req, res, auth);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (await auth.isRequestAuthorized(req)) {
|
|
78
|
+
if (url.pathname === "/auth/login" && (req.method === "GET" || req.method === "HEAD")) {
|
|
79
|
+
redirect(res, safeReturnTo(url.searchParams.get("returnTo")));
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (url.pathname === "/auth/login" && (req.method === "GET" || req.method === "HEAD")) {
|
|
85
|
+
sendLoginPage(res, { returnTo: safeReturnTo(url.searchParams.get("returnTo")) });
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (url.pathname.startsWith("/api/") || requestWantsJson(req) || req.method !== "GET" && req.method !== "HEAD") {
|
|
89
|
+
sendJson(res, 401, { error: "Authentication required." });
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
sendLoginPage(res, { returnTo: returnToFor(url) }, 401);
|
|
74
93
|
return true;
|
|
75
94
|
}
|
|
76
95
|
async function handlePasswordSetup(req, res, auth) {
|
|
77
96
|
const body = await readRequestBody(req, 32e3);
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
97
|
+
const wantsJson = requestWantsJson(req);
|
|
98
|
+
const { confirmPassword, password, returnTo } = readPasswordBody(req, body);
|
|
99
|
+
if (confirmPassword !== void 0 && password !== confirmPassword) {
|
|
100
|
+
const message = "Passwords do not match.";
|
|
101
|
+
if (wantsJson) sendJson(res, 400, { error: message });
|
|
102
|
+
else sendSetupPage(res, {
|
|
103
|
+
error: message,
|
|
104
|
+
returnTo
|
|
105
|
+
}, 400);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
81
108
|
try {
|
|
82
109
|
await auth.setupPassword(password);
|
|
83
110
|
} catch (error) {
|
|
84
111
|
const message = error instanceof Error ? error.message : "Unable to configure password.";
|
|
85
112
|
if (wantsJson) sendJson(res, 400, { error: message });
|
|
86
|
-
else sendSetupPage(res,
|
|
113
|
+
else sendSetupPage(res, {
|
|
114
|
+
error: message,
|
|
115
|
+
returnTo
|
|
116
|
+
}, 400);
|
|
87
117
|
return;
|
|
88
118
|
}
|
|
119
|
+
res.setHeader("Set-Cookie", await auth.createSessionCookie(req));
|
|
89
120
|
if (wantsJson) {
|
|
90
121
|
sendJson(res, 201, { ok: true });
|
|
91
122
|
return;
|
|
92
123
|
}
|
|
93
|
-
res
|
|
94
|
-
|
|
95
|
-
|
|
124
|
+
redirect(res, returnTo);
|
|
125
|
+
}
|
|
126
|
+
async function handlePasswordLogin(req, res, auth) {
|
|
127
|
+
const body = await readRequestBody(req, 32e3);
|
|
128
|
+
const wantsJson = requestWantsJson(req);
|
|
129
|
+
const { password, returnTo } = readPasswordBody(req, body);
|
|
130
|
+
if (!await auth.verifyPassword(password)) {
|
|
131
|
+
const message = "Password is not correct.";
|
|
132
|
+
if (wantsJson) sendJson(res, 401, { error: message });
|
|
133
|
+
else sendLoginPage(res, {
|
|
134
|
+
error: message,
|
|
135
|
+
returnTo
|
|
136
|
+
}, 401);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
res.setHeader("Set-Cookie", await auth.createSessionCookie(req));
|
|
140
|
+
if (wantsJson) {
|
|
141
|
+
sendJson(res, 200, { ok: true });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
redirect(res, returnTo);
|
|
145
|
+
}
|
|
146
|
+
function handlePasswordLogout(req, res, auth) {
|
|
147
|
+
res.setHeader("Set-Cookie", auth.clearSessionCookie());
|
|
148
|
+
if (requestWantsJson(req)) {
|
|
149
|
+
sendJson(res, 200, { ok: true });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
redirect(res, "/auth/login");
|
|
96
153
|
}
|
|
97
154
|
async function serveClientAsset(req, res, url, clientRoot) {
|
|
98
155
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
@@ -157,14 +214,55 @@ async function readRequestBody(req, limit) {
|
|
|
157
214
|
}
|
|
158
215
|
return Buffer.concat(chunks).toString("utf8");
|
|
159
216
|
}
|
|
160
|
-
function
|
|
217
|
+
function readPasswordBody(req, body) {
|
|
218
|
+
if (headerIncludes(req.headers["content-type"], "application/json")) return readJsonPasswordBody(body);
|
|
219
|
+
const values = new URLSearchParams(body);
|
|
220
|
+
return {
|
|
221
|
+
confirmPassword: values.get("confirmPassword") ?? void 0,
|
|
222
|
+
password: values.get("password") ?? "",
|
|
223
|
+
returnTo: safeReturnTo(values.get("returnTo"))
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function readJsonPasswordBody(body) {
|
|
161
227
|
try {
|
|
162
228
|
const value = JSON.parse(body);
|
|
163
|
-
|
|
229
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {
|
|
230
|
+
password: "",
|
|
231
|
+
returnTo: "/"
|
|
232
|
+
};
|
|
233
|
+
const record = value;
|
|
234
|
+
return {
|
|
235
|
+
confirmPassword: typeof record.confirmPassword === "string" ? record.confirmPassword : void 0,
|
|
236
|
+
password: typeof record.password === "string" ? record.password : "",
|
|
237
|
+
returnTo: safeReturnTo(typeof record.returnTo === "string" ? record.returnTo : null)
|
|
238
|
+
};
|
|
164
239
|
} catch {
|
|
165
|
-
return
|
|
240
|
+
return {
|
|
241
|
+
password: "",
|
|
242
|
+
returnTo: "/"
|
|
243
|
+
};
|
|
166
244
|
}
|
|
167
245
|
}
|
|
246
|
+
function requestWantsJson(req) {
|
|
247
|
+
return headerIncludes(req.headers.accept, "application/json") || headerIncludes(req.headers["content-type"], "application/json");
|
|
248
|
+
}
|
|
249
|
+
function headerIncludes(value, needle) {
|
|
250
|
+
const normalizedNeedle = needle.toLowerCase();
|
|
251
|
+
return (Array.isArray(value) ? value.join(",") : value ?? "").toLowerCase().includes(normalizedNeedle);
|
|
252
|
+
}
|
|
253
|
+
function redirect(res, location, status = 303) {
|
|
254
|
+
res.statusCode = status;
|
|
255
|
+
res.setHeader("Location", location);
|
|
256
|
+
res.end();
|
|
257
|
+
}
|
|
258
|
+
function returnToFor(url) {
|
|
259
|
+
if (url.pathname === "/auth/login" || url.pathname === "/auth/setup" || url.pathname === "/auth/logout") return safeReturnTo(url.searchParams.get("returnTo"));
|
|
260
|
+
return safeReturnTo(`${url.pathname}${url.search}`);
|
|
261
|
+
}
|
|
262
|
+
function safeReturnTo(value) {
|
|
263
|
+
if (!value || !value.startsWith("/") || value.startsWith("//") || value.includes("\0")) return "/";
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
168
266
|
function parseArgs(argv) {
|
|
169
267
|
const options = {
|
|
170
268
|
host: defaultHost,
|
|
@@ -215,7 +313,31 @@ function readPort(value) {
|
|
|
215
313
|
function invalidPort(option) {
|
|
216
314
|
throw new Error(`${option} requires a port between 1 and 65535.`);
|
|
217
315
|
}
|
|
218
|
-
function sendSetupPage(res,
|
|
316
|
+
function sendSetupPage(res, options, status = 200) {
|
|
317
|
+
sendAuthPage(res, {
|
|
318
|
+
...options,
|
|
319
|
+
action: "/auth/setup",
|
|
320
|
+
confirmPassword: true,
|
|
321
|
+
heading: "Create local password",
|
|
322
|
+
lead: "Choose the password for this pockcode workspace.",
|
|
323
|
+
passwordAutocomplete: "new-password",
|
|
324
|
+
submitLabel: "Save and continue",
|
|
325
|
+
title: "pockcode setup"
|
|
326
|
+
}, status);
|
|
327
|
+
}
|
|
328
|
+
function sendLoginPage(res, options, status = 200) {
|
|
329
|
+
sendAuthPage(res, {
|
|
330
|
+
...options,
|
|
331
|
+
action: "/auth/login",
|
|
332
|
+
confirmPassword: false,
|
|
333
|
+
heading: "Sign in",
|
|
334
|
+
lead: "Enter the local password for this pockcode workspace.",
|
|
335
|
+
passwordAutocomplete: "current-password",
|
|
336
|
+
submitLabel: "Continue",
|
|
337
|
+
title: "pockcode login"
|
|
338
|
+
}, status);
|
|
339
|
+
}
|
|
340
|
+
function sendAuthPage(res, options, status = 200) {
|
|
219
341
|
res.statusCode = status;
|
|
220
342
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
221
343
|
res.end(`<!doctype html>
|
|
@@ -223,31 +345,215 @@ function sendSetupPage(res, error, status = 200) {
|
|
|
223
345
|
<head>
|
|
224
346
|
<meta charset="utf-8" />
|
|
225
347
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
226
|
-
<title
|
|
348
|
+
<title>${escapeHtml(options.title)}</title>
|
|
227
349
|
<style>
|
|
228
|
-
:root {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
350
|
+
:root {
|
|
351
|
+
color-scheme: dark;
|
|
352
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
353
|
+
background: #111314;
|
|
354
|
+
color: #f3f4ef;
|
|
355
|
+
}
|
|
356
|
+
* { box-sizing: border-box; }
|
|
357
|
+
body {
|
|
358
|
+
min-height: 100vh;
|
|
359
|
+
margin: 0;
|
|
360
|
+
display: grid;
|
|
361
|
+
place-items: center;
|
|
362
|
+
background:
|
|
363
|
+
linear-gradient(90deg, rgb(255 255 255 / 0.03) 1px, transparent 1px),
|
|
364
|
+
linear-gradient(180deg, rgb(255 255 255 / 0.03) 1px, transparent 1px),
|
|
365
|
+
#111314;
|
|
366
|
+
background-size: 42px 42px;
|
|
367
|
+
color: #f3f4ef;
|
|
368
|
+
}
|
|
369
|
+
main {
|
|
370
|
+
width: min(880px, calc(100vw - 32px));
|
|
371
|
+
min-height: 520px;
|
|
372
|
+
display: grid;
|
|
373
|
+
grid-template-columns: minmax(0, 0.9fr) minmax(340px, 1fr);
|
|
374
|
+
overflow: hidden;
|
|
375
|
+
border: 1px solid #303433;
|
|
376
|
+
border-radius: 8px;
|
|
377
|
+
background: #181b1b;
|
|
378
|
+
box-shadow: 0 28px 90px rgb(0 0 0 / 0.42);
|
|
379
|
+
}
|
|
380
|
+
.brand {
|
|
381
|
+
display: flex;
|
|
382
|
+
min-height: 100%;
|
|
383
|
+
flex-direction: column;
|
|
384
|
+
justify-content: space-between;
|
|
385
|
+
border-right: 1px solid #303433;
|
|
386
|
+
background: #151716;
|
|
387
|
+
padding: 28px;
|
|
388
|
+
}
|
|
389
|
+
.mark {
|
|
390
|
+
display: grid;
|
|
391
|
+
width: 40px;
|
|
392
|
+
height: 40px;
|
|
393
|
+
place-items: center;
|
|
394
|
+
border: 1px solid #3d4641;
|
|
395
|
+
border-radius: 8px;
|
|
396
|
+
background: #d7ff6b;
|
|
397
|
+
color: #111314;
|
|
398
|
+
font-weight: 800;
|
|
399
|
+
letter-spacing: 0;
|
|
400
|
+
}
|
|
401
|
+
.brand h1 {
|
|
402
|
+
margin: 18px 0 6px;
|
|
403
|
+
font-size: 30px;
|
|
404
|
+
line-height: 1;
|
|
405
|
+
letter-spacing: 0;
|
|
406
|
+
}
|
|
407
|
+
.brand p, .panel p {
|
|
408
|
+
margin: 0;
|
|
409
|
+
color: #aeb7b1;
|
|
410
|
+
font-size: 14px;
|
|
411
|
+
line-height: 1.55;
|
|
412
|
+
}
|
|
413
|
+
.console {
|
|
414
|
+
display: grid;
|
|
415
|
+
gap: 10px;
|
|
416
|
+
margin-top: 30px;
|
|
417
|
+
color: #cfd7d1;
|
|
418
|
+
font-family: "SFMono-Regular", "Menlo", "Monaco", "Consolas", monospace;
|
|
419
|
+
font-size: 12px;
|
|
420
|
+
}
|
|
421
|
+
.console div {
|
|
422
|
+
display: flex;
|
|
423
|
+
justify-content: space-between;
|
|
424
|
+
gap: 18px;
|
|
425
|
+
border-bottom: 1px solid #2a2e2c;
|
|
426
|
+
padding-bottom: 10px;
|
|
427
|
+
}
|
|
428
|
+
.console span:last-child { color: #d7ff6b; }
|
|
429
|
+
.panel {
|
|
430
|
+
display: flex;
|
|
431
|
+
flex-direction: column;
|
|
432
|
+
justify-content: center;
|
|
433
|
+
padding: 44px;
|
|
434
|
+
background: #1c2020;
|
|
435
|
+
}
|
|
436
|
+
.eyebrow {
|
|
437
|
+
margin-bottom: 10px;
|
|
438
|
+
color: #8fd0ff;
|
|
439
|
+
font-size: 12px;
|
|
440
|
+
font-weight: 700;
|
|
441
|
+
letter-spacing: 0;
|
|
442
|
+
text-transform: uppercase;
|
|
443
|
+
}
|
|
444
|
+
h2 {
|
|
445
|
+
margin: 0 0 10px;
|
|
446
|
+
font-size: 26px;
|
|
447
|
+
line-height: 1.12;
|
|
448
|
+
letter-spacing: 0;
|
|
449
|
+
}
|
|
450
|
+
form {
|
|
451
|
+
display: grid;
|
|
452
|
+
gap: 14px;
|
|
453
|
+
margin-top: 28px;
|
|
454
|
+
}
|
|
455
|
+
label {
|
|
456
|
+
display: grid;
|
|
457
|
+
gap: 8px;
|
|
458
|
+
color: #f3f4ef;
|
|
459
|
+
font-size: 13px;
|
|
460
|
+
font-weight: 650;
|
|
461
|
+
}
|
|
462
|
+
input {
|
|
463
|
+
width: 100%;
|
|
464
|
+
height: 42px;
|
|
465
|
+
border: 1px solid #3a4140;
|
|
466
|
+
border-radius: 6px;
|
|
467
|
+
background: #111314;
|
|
468
|
+
color: #f3f4ef;
|
|
469
|
+
padding: 0 12px;
|
|
470
|
+
font: inherit;
|
|
471
|
+
outline: none;
|
|
472
|
+
}
|
|
473
|
+
input:focus {
|
|
474
|
+
border-color: #8fd0ff;
|
|
475
|
+
box-shadow: 0 0 0 3px rgb(143 208 255 / 0.18);
|
|
476
|
+
}
|
|
477
|
+
button {
|
|
478
|
+
height: 42px;
|
|
479
|
+
border: 0;
|
|
480
|
+
border-radius: 6px;
|
|
481
|
+
background: #d7ff6b;
|
|
482
|
+
color: #111314;
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
font: inherit;
|
|
485
|
+
font-weight: 800;
|
|
486
|
+
}
|
|
487
|
+
button:focus-visible {
|
|
488
|
+
outline: 3px solid rgb(143 208 255 / 0.38);
|
|
489
|
+
outline-offset: 2px;
|
|
490
|
+
}
|
|
491
|
+
.error {
|
|
492
|
+
margin-top: 18px;
|
|
493
|
+
border: 1px solid #8d3d3d;
|
|
494
|
+
border-radius: 6px;
|
|
495
|
+
background: #351c1e;
|
|
496
|
+
color: #ffb9b9;
|
|
497
|
+
padding: 10px 12px;
|
|
498
|
+
font-size: 13px;
|
|
499
|
+
line-height: 1.45;
|
|
500
|
+
}
|
|
501
|
+
@media (max-width: 760px) {
|
|
502
|
+
body { place-items: stretch; background-size: 34px 34px; }
|
|
503
|
+
main {
|
|
504
|
+
width: 100%;
|
|
505
|
+
min-height: 100vh;
|
|
506
|
+
grid-template-columns: 1fr;
|
|
507
|
+
border: 0;
|
|
508
|
+
border-radius: 0;
|
|
509
|
+
}
|
|
510
|
+
.brand {
|
|
511
|
+
min-height: auto;
|
|
512
|
+
border-right: 0;
|
|
513
|
+
border-bottom: 1px solid #303433;
|
|
514
|
+
padding: 24px;
|
|
515
|
+
}
|
|
516
|
+
.brand h1 { font-size: 26px; }
|
|
517
|
+
.console { display: none; }
|
|
518
|
+
.panel {
|
|
519
|
+
justify-content: start;
|
|
520
|
+
padding: 28px 24px;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
237
523
|
</style>
|
|
238
524
|
</head>
|
|
239
525
|
<body>
|
|
240
526
|
<main>
|
|
241
|
-
<
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
527
|
+
<section class="brand" aria-label="pockcode">
|
|
528
|
+
<div>
|
|
529
|
+
<div class="mark">pc</div>
|
|
530
|
+
<h1>pockcode</h1>
|
|
531
|
+
<p>Local access for your workspace.</p>
|
|
532
|
+
</div>
|
|
533
|
+
<div class="console" aria-hidden="true">
|
|
534
|
+
<div><span>host</span><span>localhost</span></div>
|
|
535
|
+
<div><span>session</span><span>locked</span></div>
|
|
536
|
+
<div><span>auth</span><span>local</span></div>
|
|
537
|
+
</div>
|
|
538
|
+
</section>
|
|
539
|
+
<section class="panel">
|
|
540
|
+
<div class="eyebrow">Workspace auth</div>
|
|
541
|
+
<h2>${escapeHtml(options.heading)}</h2>
|
|
542
|
+
<p>${escapeHtml(options.lead)}</p>
|
|
543
|
+
${options.error ? `<div class="error" role="alert">${escapeHtml(options.error)}</div>` : ""}
|
|
544
|
+
<form method="post" action="${escapeHtml(options.action)}">
|
|
545
|
+
<input name="returnTo" type="hidden" value="${escapeHtml(options.returnTo)}" />
|
|
245
546
|
<label>
|
|
246
547
|
Password
|
|
247
|
-
|
|
548
|
+
<input name="password" type="password" minlength="8" autocomplete="${options.passwordAutocomplete}" autofocus required />
|
|
248
549
|
</label>
|
|
249
|
-
|
|
250
|
-
|
|
550
|
+
${options.confirmPassword ? `<label>
|
|
551
|
+
Confirm password
|
|
552
|
+
<input name="confirmPassword" type="password" minlength="8" autocomplete="new-password" required />
|
|
553
|
+
</label>` : ""}
|
|
554
|
+
<button type="submit">${escapeHtml(options.submitLabel)}</button>
|
|
555
|
+
</form>
|
|
556
|
+
</section>
|
|
251
557
|
</main>
|
|
252
558
|
</body>
|
|
253
559
|
</html>`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pockcode",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@base-ui/react": "^1.6.0",
|
|
36
36
|
"@fontsource-variable/geist": "^5.2.9",
|
|
37
37
|
"@monaco-editor/react": "^4.7.0",
|
|
38
|
-
"@openai/codex": "^0.
|
|
38
|
+
"@openai/codex": "^0.142.5",
|
|
39
39
|
"@prisma/client": "^6.19.3",
|
|
40
40
|
"@react-router/dev": "^7.15.1",
|
|
41
41
|
"@xterm/addon-fit": "^0.11.0",
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { r as resolvePockcodeAuthPath, t as ensureParentDirectory } from "./runtime-paths.server-D6rRjGVb.js";
|
|
2
|
-
import { readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
-
import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";
|
|
4
|
-
import { promisify } from "node:util";
|
|
5
|
-
//#region app/server/auth.server.ts
|
|
6
|
-
var scrypt$1 = promisify(scrypt);
|
|
7
|
-
var passwordKeyLength = 64;
|
|
8
|
-
var passwordMinLength = 8;
|
|
9
|
-
var scryptOptions = {
|
|
10
|
-
N: 16384,
|
|
11
|
-
maxmem: 64 * 1024 * 1024,
|
|
12
|
-
p: 1,
|
|
13
|
-
r: 8
|
|
14
|
-
};
|
|
15
|
-
var authConfigCache;
|
|
16
|
-
async function hasConfiguredPassword() {
|
|
17
|
-
return Boolean(await readAuthConfig());
|
|
18
|
-
}
|
|
19
|
-
async function setupPassword(password) {
|
|
20
|
-
const normalized = password.trim();
|
|
21
|
-
if (normalized.length < passwordMinLength) throw new Error(`Password must be at least ${passwordMinLength} characters.`);
|
|
22
|
-
if (await readAuthConfig()) throw new Error("Pockcode password is already configured.");
|
|
23
|
-
const salt = randomBytes(16);
|
|
24
|
-
const key = await scrypt$1(normalized, salt, passwordKeyLength, scryptOptions);
|
|
25
|
-
const config = {
|
|
26
|
-
version: 1,
|
|
27
|
-
password: {
|
|
28
|
-
algorithm: "scrypt",
|
|
29
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30
|
-
key: key.toString("base64"),
|
|
31
|
-
keyLength: passwordKeyLength,
|
|
32
|
-
salt: salt.toString("base64"),
|
|
33
|
-
scrypt: scryptOptions
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
const authPath = resolvePockcodeAuthPath();
|
|
37
|
-
ensureParentDirectory(authPath);
|
|
38
|
-
const tmpPath = `${authPath}.${process.pid}.${Date.now()}.tmp`;
|
|
39
|
-
await writeFile(tmpPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
|
|
40
|
-
await rename(tmpPath, authPath);
|
|
41
|
-
authConfigCache = config;
|
|
42
|
-
}
|
|
43
|
-
async function verifyBasicAuthorization(header) {
|
|
44
|
-
const value = Array.isArray(header) ? header[0] : header;
|
|
45
|
-
if (!value?.startsWith("Basic ")) return false;
|
|
46
|
-
let decoded = "";
|
|
47
|
-
try {
|
|
48
|
-
decoded = Buffer.from(value.slice(6), "base64").toString("utf8");
|
|
49
|
-
} catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
const separatorIndex = decoded.indexOf(":");
|
|
53
|
-
if (separatorIndex < 0) return false;
|
|
54
|
-
return verifyPassword(decoded.slice(separatorIndex + 1));
|
|
55
|
-
}
|
|
56
|
-
function pockcodeAuthRealm() {
|
|
57
|
-
return "pockcode";
|
|
58
|
-
}
|
|
59
|
-
async function isRequestAuthorized(req) {
|
|
60
|
-
return verifyBasicAuthorization(req.headers.authorization);
|
|
61
|
-
}
|
|
62
|
-
async function verifyPassword(password) {
|
|
63
|
-
const config = await readAuthConfig();
|
|
64
|
-
if (!config) return false;
|
|
65
|
-
const expectedKey = Buffer.from(config.password.key, "base64");
|
|
66
|
-
const key = await scrypt$1(password, Buffer.from(config.password.salt, "base64"), config.password.keyLength, config.password.scrypt);
|
|
67
|
-
return key.length === expectedKey.length && timingSafeEqual(key, expectedKey);
|
|
68
|
-
}
|
|
69
|
-
async function readAuthConfig() {
|
|
70
|
-
if (authConfigCache !== void 0) return authConfigCache;
|
|
71
|
-
const authPath = resolvePockcodeAuthPath();
|
|
72
|
-
try {
|
|
73
|
-
authConfigCache = readConfig(JSON.parse(await readFile(authPath, "utf8")));
|
|
74
|
-
} catch {
|
|
75
|
-
authConfigCache = null;
|
|
76
|
-
}
|
|
77
|
-
return authConfigCache;
|
|
78
|
-
}
|
|
79
|
-
function readConfig(value) {
|
|
80
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
81
|
-
const record = value;
|
|
82
|
-
const password = record.password;
|
|
83
|
-
if (record.version !== 1 || !password || password.algorithm !== "scrypt" || typeof password.key !== "string" || typeof password.salt !== "string" || typeof password.keyLength !== "number" || !password.scrypt || typeof password.scrypt.N !== "number" || typeof password.scrypt.r !== "number" || typeof password.scrypt.p !== "number") return null;
|
|
84
|
-
return record;
|
|
85
|
-
}
|
|
86
|
-
//#endregion
|
|
87
|
-
export { hasConfiguredPassword, isRequestAuthorized, pockcodeAuthRealm, setupPassword, verifyBasicAuthorization };
|