@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.
@@ -1 +1 @@
1
- {"version":3,"file":"auth-handler.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/auth-handler.ts"],"names":[],"mappings":"AAkBA,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkC/F"}
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;IAQ3B,OAAO,CAAC,UAAU;IAYL,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;CAkFlE"}
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-DI3muo2s.js";
2
- import { A, B, M, R, c, a, g, b, d, h, e, r, s, v } from "./chunks/website-api-DI3muo2s.js";
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
- const url = this.apiBaseUrl;
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
- const url = this.apiBaseUrl;
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 url = this.apiBaseUrl;
191
- const data = await this.fetcher(url);
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: "Unable to Load 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 = { ...activeConfig, ...infra };
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, secure) {
362
+ function createSessionCookie(token, origin) {
363
363
  const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toUTCString();
364
- const SameSite = secure ? "Strict" : "Lax";
365
- return `session=${token}; HttpOnly; Secure; SameSite=${SameSite}; Path=/; Expires=${expires}`;
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 url = new URL(request.url);
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, isSecure);
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, isSecure);
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, isSecure) {
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, isSecure)
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, isSecure) {
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, isSecure)
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": "session=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0"
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
- response.headers.set("Access-Control-Allow-Origin", "same-origin");
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-DI3muo2s.js";
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-C3wLSCKU.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-MawmknFQ.js";
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,CAgBpG;AAED,wBAAgB,SAAS,IAAI,aAAa,CAEzC"}
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,4 +1,4 @@
1
- import { R, S, T, W, b, c, g, a, i, r } from "./chunks/template-MawmknFQ.js";
1
+ import { R, S, T, W, b, c, g, a, i, r } from "./chunks/template-Boh_MKY5.js";
2
2
  export {
3
3
  R as Router,
4
4
  S as SiteStore,
@@ -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;IAmBvB,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC;YAiB7B,WAAW;IAuBzB,OAAO,CAAC,kBAAkB;IAU1B,MAAM;CAoCP"}
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[]): unknown;
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,OAAO;CAG7C"}
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
@@ -1,4 +1,4 @@
1
- import { A, B, F, M, a, S } from "./chunks/index-C3wLSCKU.js";
1
+ import { A, B, F, M, a, S } from "./chunks/index-Bq8WDk9L.js";
2
2
  export {
3
3
  A as AdminPortal,
4
4
  B as BlogViewer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadertechie/personal-site-kit",
3
- "version": "0.1.0-alpha.6",
3
+ "version": "0.1.0-alpha.7",
4
4
  "type": "module",
5
5
  "description": "A high-performance personal website engine for Cloudflare Workers and R2",
6
6
  "repository": {
@@ -10,18 +10,25 @@ import {
10
10
  MAX_ATTEMPTS
11
11
  } from './auth';
12
12
 
13
- function createSessionCookie(token: string, secure: boolean): 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 SameSite = secure ? 'Strict' : 'Lax';
16
- return `session=${token}; HttpOnly; Secure; SameSite=${SameSite}; Path=/; Expires=${expires}`;
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 url = new URL(request.url);
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, isSecure);
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, isSecure);
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, isSecure: boolean): Promise<Response> {
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, isSecure)
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, isSecure: boolean): Promise<Response> {
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, isSecure)
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': 'session=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0'
191
+ 'Set-Cookie': logoutCookie
179
192
  }
180
193
  });
181
194
  }
@@ -25,21 +25,28 @@ export class WebsiteAPI {
25
25
  return response;
26
26
  }
27
27
 
28
- private addAdminCORSHeaders(response: Response): Response {
29
- response.headers.set('Access-Control-Allow-Origin', 'same-origin');
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
- activeConfig = { ...activeConfig, ...infra };
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 {
@@ -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
- const url = this.apiBaseUrl;
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
- const url = this.apiBaseUrl;
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: 'Unable to Load 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[]): unknown {
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
  }