elm-ssr 1.0.4 → 1.0.5
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/bin/elm-ssr.mjs +12 -1
- package/lib/scaffold.mjs +51 -37
- package/package.json +1 -1
package/bin/elm-ssr.mjs
CHANGED
|
@@ -13,6 +13,11 @@ const defaultRootPath = process.cwd();
|
|
|
13
13
|
const args = process.argv.slice(2);
|
|
14
14
|
const command = args[0] ?? "help";
|
|
15
15
|
|
|
16
|
+
const ownPkg = JSON.parse(
|
|
17
|
+
await readFile(new URL("../package.json", import.meta.url), "utf8")
|
|
18
|
+
);
|
|
19
|
+
const VERSION = ownPkg.version;
|
|
20
|
+
|
|
16
21
|
const findFlagValue = (flagName) => {
|
|
17
22
|
const index = args.indexOf(flagName);
|
|
18
23
|
return index >= 0 ? args[index + 1] : undefined;
|
|
@@ -71,7 +76,7 @@ const run = async (cmd, cmdArgs, cwd = rootPath) => {
|
|
|
71
76
|
};
|
|
72
77
|
|
|
73
78
|
const printHelp = () => {
|
|
74
|
-
console.log(`elm-ssr
|
|
79
|
+
console.log(`elm-ssr ${VERSION}
|
|
75
80
|
|
|
76
81
|
build Generate wrapper modules and compile configured Elm SSR apps
|
|
77
82
|
compress Pre-compress island and app bundles using Gzip for faster edge delivery
|
|
@@ -85,6 +90,7 @@ const printHelp = () => {
|
|
|
85
90
|
query Generate type-safe Elm Db modules from SQL table definitions
|
|
86
91
|
info Print current workspace package and configured app names
|
|
87
92
|
migrate ... Apply / revert / inspect SQL migrations (see: elm-ssr migrate --help)
|
|
93
|
+
version Print the elm-ssr version
|
|
88
94
|
`);
|
|
89
95
|
};
|
|
90
96
|
|
|
@@ -105,6 +111,11 @@ const requireConfig = () => {
|
|
|
105
111
|
}
|
|
106
112
|
};
|
|
107
113
|
|
|
114
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
115
|
+
console.log(VERSION);
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
switch (command) {
|
|
109
120
|
case "build":
|
|
110
121
|
case "compress":
|
package/lib/scaffold.mjs
CHANGED
|
@@ -536,6 +536,10 @@ const getConfig = (env: any): Auth0Config => ({
|
|
|
536
536
|
callbackUrl: (env?.AUTH0_CALLBACK_URL as string) ?? "http://localhost:8787/api/auth/callback",
|
|
537
537
|
});
|
|
538
538
|
|
|
539
|
+
// Use http for localhost (dev/test mock servers), https for real Auth0 domains.
|
|
540
|
+
const proto = (domain: string) =>
|
|
541
|
+
domain.startsWith("localhost") || domain.startsWith("127.") ? "http" : "https";
|
|
542
|
+
|
|
539
543
|
export const handleAuth = async (
|
|
540
544
|
request: Request,
|
|
541
545
|
env: any,
|
|
@@ -557,14 +561,14 @@ export const handleAuth = async (
|
|
|
557
561
|
redirect_uri: config.callbackUrl,
|
|
558
562
|
scope: "openid profile email",
|
|
559
563
|
});
|
|
560
|
-
return Response.redirect(
|
|
564
|
+
return Response.redirect(\`\${proto(config.domain)}://\${config.domain}/authorize?\${params}\`, 302);
|
|
561
565
|
}
|
|
562
566
|
|
|
563
567
|
if (url.pathname === "/api/auth/callback") {
|
|
564
568
|
const code = url.searchParams.get("code");
|
|
565
569
|
if (!code) return new Response("Missing code", { status: 400 });
|
|
566
570
|
|
|
567
|
-
const tokenRes = await fetch(
|
|
571
|
+
const tokenRes = await fetch(\`\${proto(config.domain)}://\${config.domain}/oauth/token\`, {
|
|
568
572
|
method: "POST",
|
|
569
573
|
headers: { "content-type": "application/json" },
|
|
570
574
|
body: JSON.stringify({
|
|
@@ -618,7 +622,7 @@ export const handleAuth = async (
|
|
|
618
622
|
status: 302,
|
|
619
623
|
headers: {
|
|
620
624
|
"Set-Cookie": "session=; Path=/; Max-Age=0; HttpOnly",
|
|
621
|
-
Location:
|
|
625
|
+
Location: \`\${proto(config.domain)}://\${config.domain}/oidc/logout?\${params}\`,
|
|
622
626
|
},
|
|
623
627
|
});
|
|
624
628
|
}
|
|
@@ -652,9 +656,10 @@ const runtimeTemplate = (appRoot, db = false, auth = undefined) => {
|
|
|
652
656
|
imports.push(`import { sessionEffects } from "elm-ssr/sessions";`);
|
|
653
657
|
imports.push(`import type { Middleware } from "elm-ssr/http";`);
|
|
654
658
|
imports.push(`import type { RequestSession } from "elm-ssr/sessions";`);
|
|
655
|
-
imports.push(`import { createAuth
|
|
659
|
+
imports.push(`import { createAuth } from "./src/Endpoints/Auth";`);
|
|
656
660
|
} else if (isAuth0) {
|
|
657
661
|
imports.push(`import { memorySessionStore } from "elm-ssr/sessions";`);
|
|
662
|
+
imports.push(`import type { Middleware } from "elm-ssr/http";`);
|
|
658
663
|
imports.push(`import { handleAuth } from "./src/Endpoints/Auth";`);
|
|
659
664
|
}
|
|
660
665
|
|
|
@@ -712,7 +717,9 @@ if (typeof Bun !== "undefined") {
|
|
|
712
717
|
}`;
|
|
713
718
|
|
|
714
719
|
const effectsConfig = isBetterAuth
|
|
715
|
-
? `,\n effects: sessionEffects(${baseEffectsBody}),\n middlewares: [betterAuthBridge]`
|
|
720
|
+
? `,\n effects: sessionEffects(${baseEffectsBody}),\n middlewares: [betterAuthHandler, betterAuthBridge]`
|
|
721
|
+
: isAuth0
|
|
722
|
+
? `,\n effects: ${baseEffectsBody},\n middlewares: [auth0Handler]`
|
|
716
723
|
: `,\n effects: ${baseEffectsBody}`;
|
|
717
724
|
|
|
718
725
|
// Auth0 keeps elm-ssr session middleware; BetterAuth manages sessions itself.
|
|
@@ -732,10 +739,30 @@ if (typeof (globalThis as any).Bun !== "undefined") {
|
|
|
732
739
|
const getAuthEnv = (env: any): any =>
|
|
733
740
|
bunAuthDb && !env?.DB ? { ...(env ?? {}), DB: bunAuthDb } : env;
|
|
734
741
|
|
|
735
|
-
//
|
|
742
|
+
// Singleton: BetterAuth is initialised once per worker isolate, not per request.
|
|
743
|
+
// Cloudflare's D1 binding is stable within an isolate; bun:sqlite is module-level too.
|
|
744
|
+
let _auth: ReturnType<typeof createAuth> | null = null;
|
|
745
|
+
const getAuth = (env: any) => (_auth ??= createAuth(getAuthEnv(env)));
|
|
746
|
+
|
|
747
|
+
// Handles all /api/auth/* requests: BetterAuth routes + the dashboard validation endpoint.
|
|
748
|
+
const betterAuthHandler: Middleware = async (context, next) => {
|
|
749
|
+
if (!context.url.pathname.startsWith("/api/auth/")) return next(context);
|
|
750
|
+
// BetterAuth's online dashboard calls this to confirm the server is reachable
|
|
751
|
+
// before saving configuration changes. Not in BetterAuth's npm package.
|
|
752
|
+
if (context.url.pathname === "/api/auth/dash/validate") {
|
|
753
|
+
const challenge = context.url.searchParams.get("challenge");
|
|
754
|
+
return new Response(challenge ?? JSON.stringify({ ok: true }), {
|
|
755
|
+
status: 200,
|
|
756
|
+
headers: { "content-type": challenge ? "text/plain" : "application/json" },
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
return getAuth(context.env).handler(context.request);
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// Bridge: reads BetterAuth session before every Elm render so Loader.requireUser works.
|
|
736
763
|
const betterAuthBridge: Middleware = async (context, next) => {
|
|
737
764
|
try {
|
|
738
|
-
const session = await
|
|
765
|
+
const session = await getAuth(context.env).api.getSession({
|
|
739
766
|
headers: context.request.headers,
|
|
740
767
|
});
|
|
741
768
|
const user = session?.user
|
|
@@ -758,43 +785,31 @@ const betterAuthBridge: Middleware = async (context, next) => {
|
|
|
758
785
|
};
|
|
759
786
|
`;
|
|
760
787
|
} else if (isAuth0) {
|
|
761
|
-
authInit =
|
|
788
|
+
authInit = `
|
|
789
|
+
export const sessionStore = memorySessionStore();
|
|
790
|
+
|
|
791
|
+
// Handles all /api/auth/* requests (login redirect, OAuth callback, logout).
|
|
792
|
+
// Runs inside elm-ssr's middleware stack so logging and error handling apply.
|
|
793
|
+
// Sets session.isNew = false to prevent sessionMiddleware from appending a
|
|
794
|
+
// competing Set-Cookie on top of the one the auth handler already set.
|
|
795
|
+
const auth0Handler: Middleware = async (context, next) => {
|
|
796
|
+
if (!context.url.pathname.startsWith("/api/auth/")) return next(context);
|
|
797
|
+
if (context.session) context.session.isNew = false;
|
|
798
|
+
const secret = (context.env?.SESSION_SECRET as string) || "change-me-to-a-secure-random-hmac-secret-key-that-is-at-least-32-chars";
|
|
799
|
+
return handleAuth(context.request, context.env, { store: sessionStore, secret });
|
|
800
|
+
};
|
|
801
|
+
`;
|
|
762
802
|
sessionsConfig = `,
|
|
763
803
|
sessions: {
|
|
764
804
|
secret: (env) => (env?.SESSION_SECRET as string) || "change-me-to-a-secure-random-hmac-secret-key-that-is-at-least-32-chars",
|
|
765
805
|
store: sessionStore,
|
|
766
806
|
secure: false
|
|
767
807
|
},
|
|
768
|
-
csrf:
|
|
808
|
+
csrf: { skipPaths: ["/api/auth/"] }`;
|
|
769
809
|
}
|
|
770
810
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
authIntercept = `
|
|
774
|
-
// Delegate all /api/auth/* requests to BetterAuth, injecting the local db when needed.
|
|
775
|
-
const baseWorkerFetch = worker.fetch;
|
|
776
|
-
worker.fetch = async (request, env, ctx) => {
|
|
777
|
-
const url = new URL(request.url);
|
|
778
|
-
if (url.pathname.startsWith("/api/auth/")) {
|
|
779
|
-
return handleAuth(request, getAuthEnv(env));
|
|
780
|
-
}
|
|
781
|
-
return baseWorkerFetch(request, env, ctx);
|
|
782
|
-
};
|
|
783
|
-
`;
|
|
784
|
-
} else if (isAuth0) {
|
|
785
|
-
authIntercept = `
|
|
786
|
-
// Delegate all /api/auth/* requests to the Auth0 handler.
|
|
787
|
-
const baseWorkerFetch = worker.fetch;
|
|
788
|
-
worker.fetch = async (request, env, ctx) => {
|
|
789
|
-
const url = new URL(request.url);
|
|
790
|
-
if (url.pathname.startsWith("/api/auth/")) {
|
|
791
|
-
const secret = (env?.SESSION_SECRET as string) || "change-me-to-a-secure-random-hmac-secret-key-that-is-at-least-32-chars";
|
|
792
|
-
return handleAuth(request, env, { store: sessionStore, secret });
|
|
793
|
-
}
|
|
794
|
-
return baseWorkerFetch(request, env, ctx);
|
|
795
|
-
};
|
|
796
|
-
`;
|
|
797
|
-
}
|
|
811
|
+
// Auth routing is handled entirely through elm-ssr's middlewares option —
|
|
812
|
+
// no worker.fetch wrapping needed.
|
|
798
813
|
|
|
799
814
|
return `${imports.join("\n")}
|
|
800
815
|
|
|
@@ -888,7 +903,6 @@ export const worker = createWorkerApp({
|
|
|
888
903
|
routes,
|
|
889
904
|
createFlags${sessionsConfig}${effectsConfig}
|
|
890
905
|
});
|
|
891
|
-
${authIntercept}
|
|
892
906
|
`;
|
|
893
907
|
};
|
|
894
908
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elm-ssr",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Elm-first SSR library and framework for Cloudflare Workers (and Bun): file-based routes/islands, backend-neutral effect adapters (KV/D1/Redis/Postgres), background tasks (waitUntil/Queues), SQL-file migrations, CLI scaffold + build.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Michał Majchrzak <michmajchrzak@gmail.com>",
|