@leadertechie/personal-site-kit 0.1.0-alpha.6 → 0.1.0-alpha.7
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/dist/api/handlers/auth-handler.d.ts.map +1 -1
- package/dist/api/website-api.d.ts.map +1 -1
- package/dist/api.js +2 -2
- package/dist/chunks/{index-C3wLSCKU.js → index-Bq8WDk9L.js} +35 -12
- package/dist/chunks/{template-MawmknFQ.js → template-Boh_MKY5.js} +2 -1
- package/dist/chunks/{website-api-DI3muo2s.js → website-api-XoeLwo_N.js} +36 -21
- package/dist/index.js +3 -3
- package/dist/shared/config/index.d.ts.map +1 -1
- package/dist/shared.js +1 -1
- package/dist/ui/about-me/index.d.ts.map +1 -1
- package/dist/ui/about-me/renderer.d.ts +2 -1
- package/dist/ui/about-me/renderer.d.ts.map +1 -1
- package/dist/ui.js +1 -1
- package/package.json +1 -1
- package/src/api/handlers/auth-handler.ts +25 -12
- package/src/api/website-api.ts +17 -9
- package/src/shared/config/index.ts +3 -1
- package/src/ui/about-me/index.ts +18 -9
- package/src/ui/about-me/renderer.ts +18 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-handler.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/auth-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth-handler.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/auth-handler.ts"],"names":[],"mappings":"AA0BA,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiC/F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-api.d.ts","sourceRoot":"","sources":["../../src/api/website-api.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE3E,qBAAa,UAAU;IACrB,OAAO,CAAC,cAAc,CAAiC;IAEhD,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU;IAIzD,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,mBAAmB;
|
|
1
|
+
{"version":3,"file":"website-api.d.ts","sourceRoot":"","sources":["../../src/api/website-api.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE3E,qBAAa,UAAU;IACrB,OAAO,CAAC,cAAc,CAAiC;IAEhD,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU;IAIzD,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,UAAU;IAgBL,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;CAmFlE"}
|
package/dist/api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { W as WebsiteAPI } from "./chunks/website-api-
|
|
2
|
-
import { A, B, M, R, c, a, g, b, d, h, e, r, s, v } from "./chunks/website-api-
|
|
1
|
+
import { W as WebsiteAPI } from "./chunks/website-api-XoeLwo_N.js";
|
|
2
|
+
import { A, B, M, R, c, a, g, b, d, h, e, r, s, v } from "./chunks/website-api-XoeLwo_N.js";
|
|
3
3
|
const defaultAPI = new WebsiteAPI();
|
|
4
4
|
export {
|
|
5
5
|
A as AUTH_KV,
|
|
@@ -86,7 +86,22 @@ const aboutmeStyles = css`
|
|
|
86
86
|
`;
|
|
87
87
|
class AboutMeRenderer {
|
|
88
88
|
renderContent(nodes) {
|
|
89
|
-
return nodes
|
|
89
|
+
return (nodes || []).map((node) => {
|
|
90
|
+
switch (node.type) {
|
|
91
|
+
case "heading":
|
|
92
|
+
const level = node.level || 2;
|
|
93
|
+
if (level === 1) return html`<h1>${node.content}</h1>`;
|
|
94
|
+
if (level === 3) return html`<h3>${node.content}</h3>`;
|
|
95
|
+
return html`<h2>${node.content}</h2>`;
|
|
96
|
+
case "paragraph":
|
|
97
|
+
return html`<p>${node.content}</p>`;
|
|
98
|
+
case "list":
|
|
99
|
+
const items = node.items || [];
|
|
100
|
+
return html`<ul>${items.map((item) => html`<li>${item}</li>`)}</ul>`;
|
|
101
|
+
default:
|
|
102
|
+
return html`<div>${node.content}</div>`;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
90
105
|
}
|
|
91
106
|
}
|
|
92
107
|
async function fetchAboutMe(url) {
|
|
@@ -167,10 +182,7 @@ class MyAboutme extends (_a$5 = LitElement, _baseUrl_dec = [property({ type: Str
|
|
|
167
182
|
this.loading = false;
|
|
168
183
|
return;
|
|
169
184
|
}
|
|
170
|
-
|
|
171
|
-
if (url) {
|
|
172
|
-
this.loadContent();
|
|
173
|
-
}
|
|
185
|
+
this.loadContent();
|
|
174
186
|
}
|
|
175
187
|
updated(changedProperties) {
|
|
176
188
|
super.updated(changedProperties);
|
|
@@ -178,17 +190,20 @@ class MyAboutme extends (_a$5 = LitElement, _baseUrl_dec = [property({ type: Str
|
|
|
178
190
|
return;
|
|
179
191
|
}
|
|
180
192
|
if (changedProperties.has("baseUrl") || changedProperties.has("base-url")) {
|
|
181
|
-
|
|
182
|
-
if (url) {
|
|
183
|
-
this.loadContent();
|
|
184
|
-
}
|
|
193
|
+
this.loadContent();
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
async loadContent() {
|
|
197
|
+
const url = this.apiBaseUrl;
|
|
198
|
+
if (!url) {
|
|
199
|
+
this.loading = false;
|
|
200
|
+
this.setFallbackContent();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
188
203
|
try {
|
|
189
204
|
this.loading = true;
|
|
190
|
-
const
|
|
191
|
-
const data = await this.fetcher(
|
|
205
|
+
const url2 = this.apiBaseUrl;
|
|
206
|
+
const data = await this.fetcher(url2);
|
|
192
207
|
this.profile = data.profile;
|
|
193
208
|
this.contentNodes = data.contentNodes;
|
|
194
209
|
this.loading = false;
|
|
@@ -202,9 +217,17 @@ class MyAboutme extends (_a$5 = LitElement, _baseUrl_dec = [property({ type: Str
|
|
|
202
217
|
setFallbackContent() {
|
|
203
218
|
this.profile = null;
|
|
204
219
|
this.contentNodes = [
|
|
220
|
+
{
|
|
221
|
+
type: "heading",
|
|
222
|
+
content: "Profile Not Found"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
type: "paragraph",
|
|
226
|
+
content: "Your about-me profile has not been initialized yet. Please run the seed command to get started:"
|
|
227
|
+
},
|
|
205
228
|
{
|
|
206
229
|
type: "paragraph",
|
|
207
|
-
content: "
|
|
230
|
+
content: "npm run seed -- <username> <password>"
|
|
208
231
|
}
|
|
209
232
|
];
|
|
210
233
|
}
|
|
@@ -14,7 +14,8 @@ const DEFAULT_STATIC = {
|
|
|
14
14
|
let activeConfig = { ...DEFAULT_INFRA, ...DEFAULT_STATIC };
|
|
15
15
|
async function initializeConfig(infra) {
|
|
16
16
|
if (infra) {
|
|
17
|
-
activeConfig =
|
|
17
|
+
if (infra.baseUrl) activeConfig.baseUrl = infra.baseUrl;
|
|
18
|
+
if (infra.apiUrl) activeConfig.apiUrl = infra.apiUrl;
|
|
18
19
|
}
|
|
19
20
|
try {
|
|
20
21
|
const res = await fetch(`${activeConfig.apiUrl}/api/static`);
|
|
@@ -359,17 +359,23 @@ async function handleWrite(request, bucket, subpath, env, method) {
|
|
|
359
359
|
}
|
|
360
360
|
return createErrorResponse("Method not allowed", 405);
|
|
361
361
|
}
|
|
362
|
-
function createSessionCookie(token,
|
|
362
|
+
function createSessionCookie(token, origin) {
|
|
363
363
|
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toUTCString();
|
|
364
|
-
const
|
|
365
|
-
|
|
364
|
+
const isSecure = origin.startsWith("https://");
|
|
365
|
+
const hostname = new URL(origin).hostname;
|
|
366
|
+
const SameSite = isSecure ? "Strict" : "Lax";
|
|
367
|
+
let cookie = `session=${token}; HttpOnly; SameSite=${SameSite}; Path=/; Expires=${expires}`;
|
|
368
|
+
if (isSecure) {
|
|
369
|
+
cookie += "; Secure";
|
|
370
|
+
}
|
|
371
|
+
cookie += `; Domain=${hostname}`;
|
|
372
|
+
return cookie;
|
|
366
373
|
}
|
|
367
374
|
async function handleAuth(request, env, subpath) {
|
|
368
375
|
request.method;
|
|
369
376
|
const clientIP = getClientIP(request);
|
|
370
377
|
const path = subpath.replace(/^\//, "").split("/")[0];
|
|
371
|
-
const
|
|
372
|
-
const isSecure = url.protocol === "https:";
|
|
378
|
+
const origin = request.headers.get("Origin") || new URL(request.url).origin;
|
|
373
379
|
const rateCheck = await checkRateLimit(env, clientIP);
|
|
374
380
|
if (!rateCheck.allowed) {
|
|
375
381
|
return new Response(JSON.stringify({
|
|
@@ -385,18 +391,18 @@ async function handleAuth(request, env, subpath) {
|
|
|
385
391
|
}
|
|
386
392
|
switch (path) {
|
|
387
393
|
case "setup":
|
|
388
|
-
return handleSetup(request, env, clientIP,
|
|
394
|
+
return handleSetup(request, env, clientIP, origin);
|
|
389
395
|
case "status":
|
|
390
396
|
return handleStatus(env);
|
|
391
397
|
case "login":
|
|
392
|
-
return handleLogin(request, env, clientIP,
|
|
398
|
+
return handleLogin(request, env, clientIP, origin);
|
|
393
399
|
case "logout":
|
|
394
400
|
return handleLogout(request, env);
|
|
395
401
|
default:
|
|
396
402
|
return createErrorResponse("Not found", 404);
|
|
397
403
|
}
|
|
398
404
|
}
|
|
399
|
-
async function handleSetup(request, env, clientIP,
|
|
405
|
+
async function handleSetup(request, env, clientIP, origin) {
|
|
400
406
|
if (request.method !== "POST") {
|
|
401
407
|
return createErrorResponse("Method not allowed", 405);
|
|
402
408
|
}
|
|
@@ -422,7 +428,7 @@ async function handleSetup(request, env, clientIP, isSecure) {
|
|
|
422
428
|
}), { expirationTtl: 7 * 24 * 60 * 60 });
|
|
423
429
|
const headers = {
|
|
424
430
|
"Content-Type": "application/json",
|
|
425
|
-
"Set-Cookie": createSessionCookie(token,
|
|
431
|
+
"Set-Cookie": createSessionCookie(token, origin)
|
|
426
432
|
};
|
|
427
433
|
return new Response(JSON.stringify({
|
|
428
434
|
success: true,
|
|
@@ -442,7 +448,7 @@ async function handleStatus(env) {
|
|
|
442
448
|
username: store?.username || null
|
|
443
449
|
});
|
|
444
450
|
}
|
|
445
|
-
async function handleLogin(request, env, clientIP,
|
|
451
|
+
async function handleLogin(request, env, clientIP, origin) {
|
|
446
452
|
if (request.method !== "POST") {
|
|
447
453
|
return createErrorResponse("Method not allowed", 405);
|
|
448
454
|
}
|
|
@@ -465,7 +471,7 @@ async function handleLogin(request, env, clientIP, isSecure) {
|
|
|
465
471
|
}), { expirationTtl: 7 * 24 * 60 * 60 });
|
|
466
472
|
const headers = {
|
|
467
473
|
"Content-Type": "application/json",
|
|
468
|
-
"Set-Cookie": createSessionCookie(token,
|
|
474
|
+
"Set-Cookie": createSessionCookie(token, origin)
|
|
469
475
|
};
|
|
470
476
|
return new Response(JSON.stringify({
|
|
471
477
|
success: true,
|
|
@@ -486,16 +492,21 @@ async function handleLogout(request, env) {
|
|
|
486
492
|
if (request.method !== "POST") {
|
|
487
493
|
return createErrorResponse("Method not allowed", 405);
|
|
488
494
|
}
|
|
495
|
+
const origin = request.headers.get("Origin") || new URL(request.url).origin;
|
|
489
496
|
const cookieHeader = request.headers.get("Cookie");
|
|
490
497
|
const sessionToken = cookieHeader?.split(";").find((c) => c.trim().startsWith("session="))?.split("=")[1];
|
|
491
498
|
if (sessionToken) {
|
|
492
499
|
await env.KV.delete(`session:${sessionToken}`);
|
|
493
500
|
}
|
|
501
|
+
const hostname = new URL(origin).hostname;
|
|
502
|
+
const isSecure = origin.startsWith("https://");
|
|
503
|
+
const SameSite = isSecure ? "Strict" : "Lax";
|
|
504
|
+
const logoutCookie = `session=; HttpOnly; SameSite=${SameSite}; Path=/; Max-Age=0; Domain=${hostname}${isSecure ? "; Secure" : ""}`;
|
|
494
505
|
return new Response(JSON.stringify({ success: true, message: "Logged out" }), {
|
|
495
506
|
status: 200,
|
|
496
507
|
headers: {
|
|
497
508
|
"Content-Type": "application/json",
|
|
498
|
-
"Set-Cookie":
|
|
509
|
+
"Set-Cookie": logoutCookie
|
|
499
510
|
}
|
|
500
511
|
});
|
|
501
512
|
}
|
|
@@ -800,28 +811,32 @@ class WebsiteAPI {
|
|
|
800
811
|
response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
801
812
|
return response;
|
|
802
813
|
}
|
|
803
|
-
addAdminCORSHeaders(response) {
|
|
804
|
-
|
|
814
|
+
addAdminCORSHeaders(response, origin) {
|
|
815
|
+
const allowOrigin = origin.includes("localhost") || origin.includes("127.0.0.1") ? origin : "same-origin";
|
|
816
|
+
response.headers.set("Access-Control-Allow-Origin", allowOrigin);
|
|
805
817
|
response.headers.set("Access-Control-Allow-Credentials", "true");
|
|
806
818
|
response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
807
819
|
response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
808
820
|
return response;
|
|
809
821
|
}
|
|
810
|
-
handleCORS() {
|
|
822
|
+
handleCORS(origin) {
|
|
823
|
+
const allowOrigin = origin.includes("localhost") || origin.includes("127.0.0.1") ? origin : "*";
|
|
811
824
|
return new Response(null, {
|
|
812
825
|
status: 200,
|
|
813
826
|
headers: {
|
|
814
|
-
"Access-Control-Allow-Origin":
|
|
827
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
815
828
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
816
829
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
830
|
+
"Access-Control-Allow-Credentials": "true",
|
|
817
831
|
"Access-Control-Max-Age": "86400"
|
|
818
832
|
}
|
|
819
833
|
});
|
|
820
834
|
}
|
|
821
835
|
async fetch(request, env) {
|
|
822
836
|
const url = new URL(request.url);
|
|
837
|
+
const origin = request.headers.get("Origin") || url.origin;
|
|
823
838
|
if (request.method === "OPTIONS") {
|
|
824
|
-
return this.handleCORS();
|
|
839
|
+
return this.handleCORS(origin);
|
|
825
840
|
}
|
|
826
841
|
const pathname = url.pathname;
|
|
827
842
|
const route = pathname.replace(/^\/api\//, "").replace(/^\//, "").replace(/\/+$/, "");
|
|
@@ -832,7 +847,7 @@ class WebsiteAPI {
|
|
|
832
847
|
try {
|
|
833
848
|
if (route === "content" || route.startsWith("content/")) {
|
|
834
849
|
const subpath = route.replace(/^content\/?/, "");
|
|
835
|
-
return this.addAdminCORSHeaders(await handleContent(request, env, subpath));
|
|
850
|
+
return this.addAdminCORSHeaders(await handleContent(request, env, subpath), origin);
|
|
836
851
|
}
|
|
837
852
|
switch (route) {
|
|
838
853
|
case "info":
|
|
@@ -844,10 +859,10 @@ class WebsiteAPI {
|
|
|
844
859
|
const sessionToken = cookieHeader?.split(";").find((c) => c.trim().startsWith("session="))?.split("=")[1];
|
|
845
860
|
const session = sessionToken ? await env.KV.get(`session:${sessionToken}`, "json") : null;
|
|
846
861
|
if (!session || session.expiresAt < Date.now()) {
|
|
847
|
-
return this.addAdminCORSHeaders(createErrorResponse("Unauthorized", 401));
|
|
862
|
+
return this.addAdminCORSHeaders(createErrorResponse("Unauthorized", 401), origin);
|
|
848
863
|
}
|
|
849
864
|
clearContentCache();
|
|
850
|
-
return this.addAdminCORSHeaders(new Response(JSON.stringify({ success: true, message: "Cache cleared" }), { status: 200 }));
|
|
865
|
+
return this.addAdminCORSHeaders(new Response(JSON.stringify({ success: true, message: "Cache cleared" }), { status: 200 }), origin);
|
|
851
866
|
case "aboutme":
|
|
852
867
|
return this.addCORSHeaders(await handleAboutMe(env));
|
|
853
868
|
case "logo":
|
|
@@ -855,7 +870,7 @@ class WebsiteAPI {
|
|
|
855
870
|
case "static":
|
|
856
871
|
return this.addCORSHeaders(await handleStaticDetails(env));
|
|
857
872
|
case "auth":
|
|
858
|
-
return this.addAdminCORSHeaders(await handleAuth(request, env, "/auth"));
|
|
873
|
+
return this.addAdminCORSHeaders(await handleAuth(request, env, "/auth"), origin);
|
|
859
874
|
case "blogs":
|
|
860
875
|
return this.addCORSHeaders(await handleBlogs(env));
|
|
861
876
|
case "blogs/latest":
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { A, B, M, R, W, c, a, g, b, d, h, e, r, s, v } from "./chunks/website-api-
|
|
1
|
+
import { A, B, M, R, W, c, a, g, b, d, h, e, r, s, v } from "./chunks/website-api-XoeLwo_N.js";
|
|
2
2
|
import { WebsitePrerender } from "./prerender.js";
|
|
3
|
-
import { A as A2, B as B2, F, M as M2, a as a2, S } from "./chunks/index-
|
|
4
|
-
import { R as R2, S as S2, T, W as W2, b as b2, c as c2, g as g2, a as a3, i, r as r2 } from "./chunks/template-
|
|
3
|
+
import { A as A2, B as B2, F, M as M2, a as a2, S } from "./chunks/index-Bq8WDk9L.js";
|
|
4
|
+
import { R as R2, S as S2, T, W as W2, b as b2, c as c2, g as g2, a as a3, i, r as r2 } from "./chunks/template-Boh_MKY5.js";
|
|
5
5
|
export {
|
|
6
6
|
A as AUTH_KV,
|
|
7
7
|
A2 as AdminPortal,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAiB,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7E,cAAc,SAAS,CAAC;AAkBxB,wBAAsB,gBAAgB,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAiB,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7E,cAAc,SAAS,CAAC;AAkBxB,wBAAsB,gBAAgB,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAkBpG;AAED,wBAAgB,SAAS,IAAI,aAAa,CAEzC"}
|
package/dist/shared.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/about-me/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAIhC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAOrC,qBACa,SAAU,SAAQ,UAAU;IACvC,MAAM,CAAC,MAAM,0BAAiB;IAG9B,QAAQ,CAAC,OAAO,SAAM;IAGtB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAQ;IAGxC,QAAQ,CAAC,YAAY,EAAE,WAAW,EAAE,CAAM;IAG1C,QAAQ,CAAC,OAAO,UAAQ;IAExB,OAAO,CAAC,QAAQ,CAAkB;IAClC;;;;OAIG;IACI,OAAO,sBAAgB;gBAElB,QAAQ,CAAC,EAAE,eAAe;IAKtC,OAAO,KAAK,UAAU,GAErB;IAEK,iBAAiB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/about-me/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAIhC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAOrC,qBACa,SAAU,SAAQ,UAAU;IACvC,MAAM,CAAC,MAAM,0BAAiB;IAG9B,QAAQ,CAAC,OAAO,SAAM;IAGtB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAQ;IAGxC,QAAQ,CAAC,YAAY,EAAE,WAAW,EAAE,CAAM;IAG1C,QAAQ,CAAC,OAAO,UAAQ;IAExB,OAAO,CAAC,QAAQ,CAAkB;IAClC;;;;OAIG;IACI,OAAO,sBAAgB;gBAElB,QAAQ,CAAC,EAAE,eAAe;IAKtC,OAAO,KAAK,UAAU,GAErB;IAEK,iBAAiB;IAgBvB,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC;YAc7B,WAAW;IA8BzB,OAAO,CAAC,kBAAkB;IAkB1B,MAAM;CAoCP"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { TemplateResult } from 'lit';
|
|
1
2
|
import { ContentNode } from '@leadertechie/md2html';
|
|
2
3
|
export declare class AboutMeRenderer {
|
|
3
|
-
renderContent(nodes: ContentNode[]):
|
|
4
|
+
renderContent(nodes: ContentNode[]): TemplateResult[];
|
|
4
5
|
}
|
|
5
6
|
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/ui/about-me/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,qBAAa,eAAe;IAC1B,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/ui/about-me/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,cAAc,EAAE,MAAM,KAAK,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,qBAAa,eAAe;IAC1B,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,cAAc,EAAE;CAkBtD"}
|
package/dist/ui.js
CHANGED
package/package.json
CHANGED
|
@@ -10,18 +10,25 @@ import {
|
|
|
10
10
|
MAX_ATTEMPTS
|
|
11
11
|
} from './auth';
|
|
12
12
|
|
|
13
|
-
function createSessionCookie(token: string,
|
|
13
|
+
function createSessionCookie(token: string, origin: string): string {
|
|
14
14
|
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toUTCString();
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const isSecure = origin.startsWith('https://');
|
|
16
|
+
const hostname = new URL(origin).hostname;
|
|
17
|
+
const SameSite = isSecure ? 'Strict' : 'Lax';
|
|
18
|
+
|
|
19
|
+
let cookie = `session=${token}; HttpOnly; SameSite=${SameSite}; Path=/; Expires=${expires}`;
|
|
20
|
+
if (isSecure) {
|
|
21
|
+
cookie += '; Secure';
|
|
22
|
+
}
|
|
23
|
+
cookie += `; Domain=${hostname}`;
|
|
24
|
+
return cookie;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
export async function handleAuth(request: Request, env: any, subpath: string): Promise<Response> {
|
|
20
28
|
const method = request.method;
|
|
21
29
|
const clientIP = getClientIP(request);
|
|
22
30
|
const path = subpath.replace(/^\//, '').split('/')[0];
|
|
23
|
-
const
|
|
24
|
-
const isSecure = url.protocol === 'https:';
|
|
31
|
+
const origin = request.headers.get('Origin') || new URL(request.url).origin;
|
|
25
32
|
|
|
26
33
|
// Check rate limit for login attempts
|
|
27
34
|
const rateCheck = await checkRateLimit(env, clientIP);
|
|
@@ -40,11 +47,11 @@ export async function handleAuth(request: Request, env: any, subpath: string): P
|
|
|
40
47
|
|
|
41
48
|
switch (path) {
|
|
42
49
|
case 'setup':
|
|
43
|
-
return handleSetup(request, env, clientIP,
|
|
50
|
+
return handleSetup(request, env, clientIP, origin);
|
|
44
51
|
case 'status':
|
|
45
52
|
return handleStatus(env);
|
|
46
53
|
case 'login':
|
|
47
|
-
return handleLogin(request, env, clientIP,
|
|
54
|
+
return handleLogin(request, env, clientIP, origin);
|
|
48
55
|
case 'logout':
|
|
49
56
|
return handleLogout(request, env);
|
|
50
57
|
default:
|
|
@@ -52,7 +59,7 @@ export async function handleAuth(request: Request, env: any, subpath: string): P
|
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
async function handleSetup(request: Request, env: any, clientIP: string,
|
|
62
|
+
async function handleSetup(request: Request, env: any, clientIP: string, origin: string): Promise<Response> {
|
|
56
63
|
if (request.method !== 'POST') {
|
|
57
64
|
return createErrorResponse('Method not allowed', 405);
|
|
58
65
|
}
|
|
@@ -85,7 +92,7 @@ async function handleSetup(request: Request, env: any, clientIP: string, isSecur
|
|
|
85
92
|
|
|
86
93
|
const headers: Record<string, string> = {
|
|
87
94
|
'Content-Type': 'application/json',
|
|
88
|
-
'Set-Cookie': createSessionCookie(token,
|
|
95
|
+
'Set-Cookie': createSessionCookie(token, origin)
|
|
89
96
|
};
|
|
90
97
|
|
|
91
98
|
return new Response(JSON.stringify({
|
|
@@ -109,7 +116,7 @@ async function handleStatus(env: any): Promise<Response> {
|
|
|
109
116
|
});
|
|
110
117
|
}
|
|
111
118
|
|
|
112
|
-
async function handleLogin(request: Request, env: any, clientIP: string,
|
|
119
|
+
async function handleLogin(request: Request, env: any, clientIP: string, origin: string): Promise<Response> {
|
|
113
120
|
if (request.method !== 'POST') {
|
|
114
121
|
return createErrorResponse('Method not allowed', 405);
|
|
115
122
|
}
|
|
@@ -138,7 +145,7 @@ async function handleLogin(request: Request, env: any, clientIP: string, isSecur
|
|
|
138
145
|
|
|
139
146
|
const headers: Record<string, string> = {
|
|
140
147
|
'Content-Type': 'application/json',
|
|
141
|
-
'Set-Cookie': createSessionCookie(token,
|
|
148
|
+
'Set-Cookie': createSessionCookie(token, origin)
|
|
142
149
|
};
|
|
143
150
|
|
|
144
151
|
return new Response(JSON.stringify({
|
|
@@ -162,6 +169,7 @@ async function handleLogout(request: Request, env: any): Promise<Response> {
|
|
|
162
169
|
return createErrorResponse('Method not allowed', 405);
|
|
163
170
|
}
|
|
164
171
|
|
|
172
|
+
const origin = request.headers.get('Origin') || new URL(request.url).origin;
|
|
165
173
|
const cookieHeader = request.headers.get('Cookie');
|
|
166
174
|
const sessionToken = cookieHeader?.split(';')
|
|
167
175
|
.find(c => c.trim().startsWith('session='))
|
|
@@ -171,11 +179,16 @@ async function handleLogout(request: Request, env: any): Promise<Response> {
|
|
|
171
179
|
await env.KV.delete(`session:${sessionToken}`);
|
|
172
180
|
}
|
|
173
181
|
|
|
182
|
+
const hostname = new URL(origin).hostname;
|
|
183
|
+
const isSecure = origin.startsWith('https://');
|
|
184
|
+
const SameSite = isSecure ? 'Strict' : 'Lax';
|
|
185
|
+
const logoutCookie = `session=; HttpOnly; SameSite=${SameSite}; Path=/; Max-Age=0; Domain=${hostname}${isSecure ? '; Secure' : ''}`;
|
|
186
|
+
|
|
174
187
|
return new Response(JSON.stringify({ success: true, message: 'Logged out' }), {
|
|
175
188
|
status: 200,
|
|
176
189
|
headers: {
|
|
177
190
|
'Content-Type': 'application/json',
|
|
178
|
-
'Set-Cookie':
|
|
191
|
+
'Set-Cookie': logoutCookie
|
|
179
192
|
}
|
|
180
193
|
});
|
|
181
194
|
}
|
package/src/api/website-api.ts
CHANGED
|
@@ -25,21 +25,28 @@ export class WebsiteAPI {
|
|
|
25
25
|
return response;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
private addAdminCORSHeaders(response: Response): Response {
|
|
29
|
-
|
|
28
|
+
private addAdminCORSHeaders(response: Response, origin: string): Response {
|
|
29
|
+
const allowOrigin = origin.includes('localhost') || origin.includes('127.0.0.1')
|
|
30
|
+
? origin
|
|
31
|
+
: 'same-origin';
|
|
32
|
+
response.headers.set('Access-Control-Allow-Origin', allowOrigin);
|
|
30
33
|
response.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
31
34
|
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
32
35
|
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
33
36
|
return response;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
private handleCORS(): Response {
|
|
39
|
+
private handleCORS(origin: string): Response {
|
|
40
|
+
const allowOrigin = origin.includes('localhost') || origin.includes('127.0.0.1')
|
|
41
|
+
? origin
|
|
42
|
+
: '*';
|
|
37
43
|
return new Response(null, {
|
|
38
44
|
status: 200,
|
|
39
45
|
headers: {
|
|
40
|
-
'Access-Control-Allow-Origin':
|
|
46
|
+
'Access-Control-Allow-Origin': allowOrigin ,
|
|
41
47
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' ,
|
|
42
48
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
49
|
+
'Access-Control-Allow-Credentials': 'true',
|
|
43
50
|
'Access-Control-Max-Age': '86400',
|
|
44
51
|
},
|
|
45
52
|
});
|
|
@@ -47,9 +54,10 @@ export class WebsiteAPI {
|
|
|
47
54
|
|
|
48
55
|
public async fetch(request: Request, env: any): Promise<Response> {
|
|
49
56
|
const url = new URL(request.url);
|
|
57
|
+
const origin = request.headers.get('Origin') || url.origin;
|
|
50
58
|
|
|
51
59
|
if (request.method === 'OPTIONS') {
|
|
52
|
-
return this.handleCORS();
|
|
60
|
+
return this.handleCORS(origin);
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
const pathname = url.pathname;
|
|
@@ -68,7 +76,7 @@ export class WebsiteAPI {
|
|
|
68
76
|
// Check for content route first (content/*)
|
|
69
77
|
if (route === 'content' || route.startsWith('content/')) {
|
|
70
78
|
const subpath = route.replace(/^content\/?/, '');
|
|
71
|
-
return this.addAdminCORSHeaders(await handleContent(request, env, subpath));
|
|
79
|
+
return this.addAdminCORSHeaders(await handleContent(request, env, subpath), origin);
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
switch (route) {
|
|
@@ -83,10 +91,10 @@ export class WebsiteAPI {
|
|
|
83
91
|
?.split('=')[1];
|
|
84
92
|
const session = sessionToken ? await env.KV.get(`session:${sessionToken}`, 'json') : null;
|
|
85
93
|
if (!session || session.expiresAt < Date.now()) {
|
|
86
|
-
return this.addAdminCORSHeaders(createErrorResponse('Unauthorized', 401));
|
|
94
|
+
return this.addAdminCORSHeaders(createErrorResponse('Unauthorized', 401), origin);
|
|
87
95
|
}
|
|
88
96
|
clearContentCache();
|
|
89
|
-
return this.addAdminCORSHeaders(new Response(JSON.stringify({ success: true, message: 'Cache cleared' }), { status: 200 }));
|
|
97
|
+
return this.addAdminCORSHeaders(new Response(JSON.stringify({ success: true, message: 'Cache cleared' }), { status: 200 }), origin);
|
|
90
98
|
case 'aboutme':
|
|
91
99
|
return this.addCORSHeaders(await handleAboutMe(env));
|
|
92
100
|
case 'logo':
|
|
@@ -94,7 +102,7 @@ export class WebsiteAPI {
|
|
|
94
102
|
case 'static':
|
|
95
103
|
return this.addCORSHeaders(await handleStaticDetails(env));
|
|
96
104
|
case 'auth':
|
|
97
|
-
return this.addAdminCORSHeaders(await handleAuth(request, env, '/auth'));
|
|
105
|
+
return this.addAdminCORSHeaders(await handleAuth(request, env, '/auth'), origin);
|
|
98
106
|
case 'blogs':
|
|
99
107
|
return this.addCORSHeaders(await handleBlogs(env));
|
|
100
108
|
case 'blogs/latest':
|
|
@@ -20,7 +20,9 @@ let activeConfig: WebsiteConfig = { ...DEFAULT_INFRA, ...DEFAULT_STATIC };
|
|
|
20
20
|
|
|
21
21
|
export async function initializeConfig(infra?: Partial<InfrastructureConfig>): Promise<WebsiteConfig> {
|
|
22
22
|
if (infra) {
|
|
23
|
-
|
|
23
|
+
// Only merge defined values
|
|
24
|
+
if (infra.baseUrl) activeConfig.baseUrl = infra.baseUrl;
|
|
25
|
+
if (infra.apiUrl) activeConfig.apiUrl = infra.apiUrl;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
try {
|
package/src/ui/about-me/index.ts
CHANGED
|
@@ -60,10 +60,7 @@ export class MyAboutme extends LitElement {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Load content if a baseUrl is provided. In dev/prod, this will be set.
|
|
63
|
-
|
|
64
|
-
if (url) {
|
|
65
|
-
this.loadContent();
|
|
66
|
-
}
|
|
63
|
+
this.loadContent();
|
|
67
64
|
}
|
|
68
65
|
|
|
69
66
|
updated(changedProperties: Map<string, any>) {
|
|
@@ -76,14 +73,18 @@ export class MyAboutme extends LitElement {
|
|
|
76
73
|
|
|
77
74
|
// If baseUrl changed and we have a valid baseUrl, load content
|
|
78
75
|
if (changedProperties.has('baseUrl') || changedProperties.has('base-url')) {
|
|
79
|
-
|
|
80
|
-
if (url) {
|
|
81
|
-
this.loadContent();
|
|
82
|
-
}
|
|
76
|
+
this.loadContent();
|
|
83
77
|
}
|
|
84
78
|
}
|
|
85
79
|
|
|
86
80
|
private async loadContent() {
|
|
81
|
+
const url = this.apiBaseUrl;
|
|
82
|
+
if (!url) {
|
|
83
|
+
this.loading = false;
|
|
84
|
+
this.setFallbackContent();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
87
88
|
try {
|
|
88
89
|
this.loading = true;
|
|
89
90
|
|
|
@@ -109,9 +110,17 @@ export class MyAboutme extends LitElement {
|
|
|
109
110
|
private setFallbackContent() {
|
|
110
111
|
this.profile = null; // No profile data
|
|
111
112
|
this.contentNodes = [
|
|
113
|
+
{
|
|
114
|
+
type: 'heading',
|
|
115
|
+
content: 'Profile Not Found'
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'paragraph',
|
|
119
|
+
content: 'Your about-me profile has not been initialized yet. Please run the seed command to get started:'
|
|
120
|
+
},
|
|
112
121
|
{
|
|
113
122
|
type: 'paragraph',
|
|
114
|
-
content: '
|
|
123
|
+
content: 'npm run seed -- <username> <password>'
|
|
115
124
|
}
|
|
116
125
|
];
|
|
117
126
|
}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
import { html, TemplateResult } from 'lit';
|
|
1
2
|
import { ContentNode } from '@leadertechie/md2html';
|
|
2
3
|
|
|
3
4
|
export class AboutMeRenderer {
|
|
4
|
-
renderContent(nodes: ContentNode[]):
|
|
5
|
-
return nodes
|
|
5
|
+
renderContent(nodes: ContentNode[]): TemplateResult[] {
|
|
6
|
+
return (nodes || []).map(node => {
|
|
7
|
+
switch (node.type) {
|
|
8
|
+
case 'heading':
|
|
9
|
+
const level = (node as any).level || 2;
|
|
10
|
+
if (level === 1) return html`<h1>${node.content}</h1>`;
|
|
11
|
+
if (level === 3) return html`<h3>${node.content}</h3>`;
|
|
12
|
+
return html`<h2>${node.content}</h2>`;
|
|
13
|
+
case 'paragraph':
|
|
14
|
+
return html`<p>${node.content}</p>`;
|
|
15
|
+
case 'list':
|
|
16
|
+
const items = (node as any).items || [];
|
|
17
|
+
return html`<ul>${items.map((item: string) => html`<li>${item}</li>`)}</ul>`;
|
|
18
|
+
default:
|
|
19
|
+
return html`<div>${node.content}</div>`;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
6
22
|
}
|
|
7
23
|
}
|