@getstrata/starter 1.1.5 → 1.1.6
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/cli.js +113 -16
- package/dist/templates/package.json +3 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -120,7 +120,7 @@ function extraApplies(extra, auth) {
|
|
|
120
120
|
if (extra === "metrics" || extra === "billing" || extra === "webhooks") {
|
|
121
121
|
return true;
|
|
122
122
|
}
|
|
123
|
-
if (extra === "mfa" || extra === "oauthGithub") {
|
|
123
|
+
if (extra === "mfa" || extra === "oauthGithub" || extra === "oidc") {
|
|
124
124
|
return htmlAuthKit(auth);
|
|
125
125
|
}
|
|
126
126
|
return authNeedsUsers(auth);
|
|
@@ -196,6 +196,7 @@ var noneExtras = {
|
|
|
196
196
|
scim: false,
|
|
197
197
|
metrics: false,
|
|
198
198
|
oauthGithub: false,
|
|
199
|
+
oidc: false,
|
|
199
200
|
billing: false,
|
|
200
201
|
webhooks: false
|
|
201
202
|
};
|
|
@@ -205,6 +206,7 @@ var enterpriseExtras = {
|
|
|
205
206
|
scim: true,
|
|
206
207
|
metrics: true,
|
|
207
208
|
oauthGithub: false,
|
|
209
|
+
oidc: false,
|
|
208
210
|
billing: false,
|
|
209
211
|
webhooks: false
|
|
210
212
|
};
|
|
@@ -274,9 +276,10 @@ Options:
|
|
|
274
276
|
--scim / --no-scim
|
|
275
277
|
--metrics / --no-metrics
|
|
276
278
|
--oauth-github / --no-oauth-github
|
|
279
|
+
--oidc / --no-oidc
|
|
277
280
|
--billing / --no-billing
|
|
278
281
|
--webhooks / --no-webhooks
|
|
279
|
-
--extras Interactive extras list (MFA, email verification, SCIM, metrics, GitHub OAuth, billing, webhooks)
|
|
282
|
+
--extras Interactive extras list (MFA, email verification, SCIM, metrics, GitHub OAuth, OIDC, billing, webhooks)
|
|
280
283
|
--docker Write Docker Compose for every selected tool that needs a service
|
|
281
284
|
--no-docker Skip docker-compose.yml; use installs already on this machine
|
|
282
285
|
--docker-services Subset: postgres, mysql, redis, mailpit, adminer (comma-separated)
|
|
@@ -382,6 +385,8 @@ function parseCreateStrataArgs(argv) {
|
|
|
382
385
|
["--no-metrics", "metrics", false],
|
|
383
386
|
["--oauth-github", "oauthGithub", true],
|
|
384
387
|
["--no-oauth-github", "oauthGithub", false],
|
|
388
|
+
["--oidc", "oidc", true],
|
|
389
|
+
["--no-oidc", "oidc", false],
|
|
385
390
|
["--billing", "billing", true],
|
|
386
391
|
["--no-billing", "billing", false],
|
|
387
392
|
["--webhooks", "webhooks", true],
|
|
@@ -509,6 +514,9 @@ function extraFlagName(extra) {
|
|
|
509
514
|
if (extra === "oauthGithub") {
|
|
510
515
|
return "oauth-github";
|
|
511
516
|
}
|
|
517
|
+
if (extra === "oidc") {
|
|
518
|
+
return "oidc";
|
|
519
|
+
}
|
|
512
520
|
return extra;
|
|
513
521
|
}
|
|
514
522
|
function dropInapplicableExtras(layers, flags) {
|
|
@@ -912,6 +920,7 @@ var EXTRA_CHOICES = [
|
|
|
912
920
|
{ value: "scim", label: "scim: /Users adapter" },
|
|
913
921
|
{ value: "metrics", label: "metrics: Prometheus token" },
|
|
914
922
|
{ value: "oauthGithub", label: "oauth-github: GitHub cookie login at /auth/github" },
|
|
923
|
+
{ value: "oidc", label: "oidc: OIDC cookie login at /auth/oidc (PKCE cookie)" },
|
|
915
924
|
{ value: "billing", label: "billing: Stripe webhook stub + GET /api/v1/billing/subscription" },
|
|
916
925
|
{ value: "webhooks", label: "webhooks: outbound notes.created job (webhook.dispatch)" }
|
|
917
926
|
];
|
|
@@ -1200,6 +1209,7 @@ function renderAuthModule(layers) {
|
|
|
1200
1209
|
}
|
|
1201
1210
|
const cookie = htmlAuthKit(layers.auth);
|
|
1202
1211
|
const github = Boolean(layers.extras.oauthGithub && cookie);
|
|
1212
|
+
const oidc = Boolean(layers.extras.oidc && cookie);
|
|
1203
1213
|
const mfa = Boolean(layers.extras.mfa && cookie);
|
|
1204
1214
|
const verify = Boolean(layers.extras.emailVerification);
|
|
1205
1215
|
const jsonApi = authUsesToken(layers.auth) || authUsesJwt(layers.auth);
|
|
@@ -1221,6 +1231,11 @@ function renderAuthModule(layers) {
|
|
|
1221
1231
|
imports.push(`import { createSamlServiceProvider } from "@getstrata/core/auth/saml/samlServiceProvider";`);
|
|
1222
1232
|
if (github) {
|
|
1223
1233
|
imports.push(`import { GitHubOAuthProvider } from "@getstrata/core/auth/oauth/providers";`);
|
|
1234
|
+
}
|
|
1235
|
+
if (oidc) {
|
|
1236
|
+
imports.push(`import { OidcProvider } from "@getstrata/core/auth/oauth/oidcProvider";`);
|
|
1237
|
+
}
|
|
1238
|
+
if (github || oidc) {
|
|
1224
1239
|
imports.push(`import { appUrl } from "@getstrata/core/runtime/appKeyPrefix";`);
|
|
1225
1240
|
}
|
|
1226
1241
|
}
|
|
@@ -1251,7 +1266,7 @@ function renderAuthModule(layers) {
|
|
|
1251
1266
|
if (mfa) {
|
|
1252
1267
|
imports.push(`import { sanitizeInternalPath } from "@getstrata/core/http/safeInternalPath";`);
|
|
1253
1268
|
}
|
|
1254
|
-
imports.push(`import { createOAuthState, verifyOAuthState } from "@getstrata/core/security/oauthState";`);
|
|
1269
|
+
imports.push(oidc ? `import { clearOidcPkceCookie, createOAuthState, readOidcPkceCookie, sealOidcPkceCookie, verifyOAuthState } from "@getstrata/core/security/oauthState";` : `import { createOAuthState, verifyOAuthState } from "@getstrata/core/security/oauthState";`);
|
|
1255
1270
|
}
|
|
1256
1271
|
if (jsonApi || cookie) {
|
|
1257
1272
|
imports.push(`import { jsonResponse, withErrorHandling } from "@getstrata/core/http/response";`);
|
|
@@ -1692,9 +1707,67 @@ async function completeBrowserSsoLogin(
|
|
|
1692
1707
|
return completeBrowserSsoLogin(dependencies, profile, "GitHub");
|
|
1693
1708
|
})),
|
|
1694
1709
|
},` : "";
|
|
1710
|
+
const oidcRoutes = oidc ? `
|
|
1711
|
+
"/auth/oidc": {
|
|
1712
|
+
GET: kernel.wrap("api", withErrorHandling(async () => {
|
|
1713
|
+
if (process.env.FEATURE_OAUTH !== "true") {
|
|
1714
|
+
return new Response("Not found", { status: 404 });
|
|
1715
|
+
}
|
|
1716
|
+
const issuer = process.env.OIDC_ISSUER?.trim();
|
|
1717
|
+
const clientId = process.env.OIDC_CLIENT_ID?.trim();
|
|
1718
|
+
const clientSecret = process.env.OIDC_CLIENT_SECRET?.trim();
|
|
1719
|
+
if (!issuer || !clientId || !clientSecret) {
|
|
1720
|
+
return new Response("Not found", { status: 404 });
|
|
1721
|
+
}
|
|
1722
|
+
const redirectUri = process.env.OIDC_REDIRECT_URI?.trim() || \`\${appUrl()}/auth/oidc/callback\`;
|
|
1723
|
+
const issued = createOAuthState();
|
|
1724
|
+
const provider = new OidcProvider({ name: "oidc", issuer, clientId, clientSecret, redirectUri });
|
|
1725
|
+
const { url, handshake } = await provider.createAuthorization(issued.state);
|
|
1726
|
+
const redirect = new Response(null, { status: 302, headers: { location: url } });
|
|
1727
|
+
redirect.headers.append(
|
|
1728
|
+
"set-cookie",
|
|
1729
|
+
sealOidcPkceCookie({ state: issued.state, nonce: handshake.nonce, codeVerifier: handshake.codeVerifier }),
|
|
1730
|
+
);
|
|
1731
|
+
return redirect;
|
|
1732
|
+
})),
|
|
1733
|
+
},
|
|
1734
|
+
"/auth/oidc/callback": {
|
|
1735
|
+
GET: kernel.wrap("api", withErrorHandling(async (request) => {
|
|
1736
|
+
if (process.env.FEATURE_OAUTH !== "true") {
|
|
1737
|
+
return new Response("Not found", { status: 404 });
|
|
1738
|
+
}
|
|
1739
|
+
const issuer = process.env.OIDC_ISSUER?.trim();
|
|
1740
|
+
const clientId = process.env.OIDC_CLIENT_ID?.trim();
|
|
1741
|
+
const clientSecret = process.env.OIDC_CLIENT_SECRET?.trim();
|
|
1742
|
+
if (!issuer || !clientId || !clientSecret) {
|
|
1743
|
+
return new Response("Not found", { status: 404 });
|
|
1744
|
+
}
|
|
1745
|
+
const url = new URL(request.url);
|
|
1746
|
+
const code = url.searchParams.get("code");
|
|
1747
|
+
const state = url.searchParams.get("state");
|
|
1748
|
+
const pkce = readOidcPkceCookie(request, state);
|
|
1749
|
+
if (!code || !verifyOAuthState(request, state) || !pkce) {
|
|
1750
|
+
const denied = jsonResponse({ error: "Invalid OIDC state." }, { status: 403 });
|
|
1751
|
+
denied.headers.append("set-cookie", clearOidcPkceCookie());
|
|
1752
|
+
return denied;
|
|
1753
|
+
}
|
|
1754
|
+
const redirectUri = process.env.OIDC_REDIRECT_URI?.trim() || \`\${appUrl()}/auth/oidc/callback\`;
|
|
1755
|
+
const profile = await new OidcProvider({ name: "oidc", issuer, clientId, clientSecret, redirectUri })
|
|
1756
|
+
.exchangeCode(code, redirectUri, pkce)
|
|
1757
|
+
.catch(() => null);
|
|
1758
|
+
if (!profile) {
|
|
1759
|
+
const failed = jsonResponse({ error: "Invalid OIDC response." }, { status: 400 });
|
|
1760
|
+
failed.headers.append("set-cookie", clearOidcPkceCookie());
|
|
1761
|
+
return failed;
|
|
1762
|
+
}
|
|
1763
|
+
const login = await completeBrowserSsoLogin(dependencies, profile, "OIDC");
|
|
1764
|
+
login.headers.append("set-cookie", clearOidcPkceCookie());
|
|
1765
|
+
return login;
|
|
1766
|
+
})),
|
|
1767
|
+
},` : "";
|
|
1695
1768
|
const apiRoutes = jsonApi || cookie ? `
|
|
1696
1769
|
routes({ kernel, dependencies }) {
|
|
1697
|
-
return {${csrfRoute}${samlRoutes}${githubRoutes}${cookieJsonAuth}${tokenLogin}${jsonRegister}${jwtLogin}${apiUser}${jsonPassword}${jsonVerify}
|
|
1770
|
+
return {${csrfRoute}${samlRoutes}${githubRoutes}${oidcRoutes}${cookieJsonAuth}${tokenLogin}${jsonRegister}${jwtLogin}${apiUser}${jsonPassword}${jsonVerify}
|
|
1698
1771
|
};
|
|
1699
1772
|
},` : "";
|
|
1700
1773
|
const mfaLoginBranch = `const mfaResult = completePasswordLogin(user, { mfaCode: mfa_code });
|
|
@@ -2311,9 +2384,11 @@ function textField(name, label, type, extra = "") {
|
|
|
2311
2384
|
function renderLoginView(layers) {
|
|
2312
2385
|
const githubLink = layers.extras.oauthGithub && htmlAuthKit(layers.auth) ? `
|
|
2313
2386
|
<a href="/auth/github">Sign in with GitHub</a>` : "";
|
|
2387
|
+
const oidcLink = layers.extras.oidc && htmlAuthKit(layers.auth) ? `
|
|
2388
|
+
<a href="/auth/oidc">Sign in with OIDC</a>` : "";
|
|
2314
2389
|
return renderFormView("Sign in", `${textField("email", "Email", "email", 'required autocomplete="username"')}
|
|
2315
2390
|
${textField("password", "Password", "password", 'required autocomplete="current-password"')}`, "Sign in", `<a href="/register">Create account</a>
|
|
2316
|
-
<a href="/forgot-password">Forgot password</a>${githubLink}`).replace('action="<%= it.action || "" %>"', 'action="/login"');
|
|
2391
|
+
<a href="/forgot-password">Forgot password</a>${githubLink}${oidcLink}`).replace('action="<%= it.action || "" %>"', 'action="/login"');
|
|
2317
2392
|
}
|
|
2318
2393
|
function renderRegisterView() {
|
|
2319
2394
|
return renderFormView("Create account", `${textField("name", "Name", "text", "required")}
|
|
@@ -2734,20 +2809,38 @@ function renderEnvExample(projectName, layers) {
|
|
|
2734
2809
|
lines.push("# SAML_IDP_ISSUER=");
|
|
2735
2810
|
lines.push("# SAML_WANT_RESPONSE_SIGNED=");
|
|
2736
2811
|
lines.push("# SAML_DISABLE_REQUESTED_AUTHN_CONTEXT=");
|
|
2737
|
-
const
|
|
2738
|
-
|
|
2739
|
-
|
|
2812
|
+
const githubOn = Boolean(layers.extras.oauthGithub);
|
|
2813
|
+
const oidcOn = Boolean(layers.extras.oidc);
|
|
2814
|
+
lines.push(`FEATURE_OAUTH=${envFlag(githubOn || oidcOn)}`);
|
|
2815
|
+
if (githubOn) {
|
|
2740
2816
|
lines.push("GITHUB_CLIENT_ID=");
|
|
2741
2817
|
lines.push("GITHUB_CLIENT_SECRET=");
|
|
2742
2818
|
lines.push("GITHUB_REDIRECT_URI=http://localhost:3000/auth/github/callback");
|
|
2743
|
-
lines.push("OAUTH_STATE_SECRET=dev-oauth-state-secret-change-me-please-32");
|
|
2744
2819
|
} else {
|
|
2745
2820
|
lines.push("# GITHUB_CLIENT_ID=");
|
|
2746
2821
|
lines.push("# GITHUB_CLIENT_SECRET=");
|
|
2747
2822
|
lines.push("# GITHUB_REDIRECT_URI=http://localhost:3000/auth/github/callback");
|
|
2823
|
+
}
|
|
2824
|
+
if (oidcOn) {
|
|
2825
|
+
lines.push("OIDC_ISSUER=http://127.0.0.1:8080");
|
|
2826
|
+
lines.push("OIDC_CLIENT_ID=");
|
|
2827
|
+
lines.push("OIDC_CLIENT_SECRET=");
|
|
2828
|
+
lines.push("OIDC_REDIRECT_URI=http://localhost:3000/auth/oidc/callback");
|
|
2829
|
+
lines.push("# Loopback issuers are allowed when APP_ENV is not production. Other private IdPs:");
|
|
2830
|
+
lines.push("# OIDC_ALLOW_PRIVATE=true");
|
|
2831
|
+
} else {
|
|
2832
|
+
lines.push("# OIDC_ISSUER=");
|
|
2833
|
+
lines.push("# OIDC_CLIENT_ID=");
|
|
2834
|
+
lines.push("# OIDC_CLIENT_SECRET=");
|
|
2835
|
+
lines.push("# OIDC_REDIRECT_URI=http://localhost:3000/auth/oidc/callback");
|
|
2836
|
+
lines.push("# OIDC_ALLOW_PRIVATE=true");
|
|
2837
|
+
}
|
|
2838
|
+
if (githubOn || oidcOn) {
|
|
2839
|
+
lines.push("OAUTH_STATE_SECRET=dev-oauth-state-secret-change-me-please-32");
|
|
2840
|
+
} else {
|
|
2748
2841
|
lines.push("# OAUTH_STATE_SECRET=");
|
|
2749
2842
|
}
|
|
2750
|
-
lines.push("#
|
|
2843
|
+
lines.push("# --oidc (cookie HTML) seals PKCE in the oidc_pkce cookie. Do not call OidcProvider.getAuthorizationUrl().");
|
|
2751
2844
|
const billingOn = Boolean(layers.extras.billing);
|
|
2752
2845
|
lines.push(`FEATURE_BILLING=${envFlag(billingOn)}`);
|
|
2753
2846
|
if (billingOn) {
|
|
@@ -2966,9 +3059,9 @@ function renderPackageJson(projectName, options = {}) {
|
|
|
2966
3059
|
"@getstrata/cli": "workspace:*",
|
|
2967
3060
|
"@getstrata/core": "workspace:*"
|
|
2968
3061
|
} : {
|
|
2969
|
-
"@getstrata/bootstrap": "^1.1.
|
|
2970
|
-
"@getstrata/cli": "^1.1.
|
|
2971
|
-
"@getstrata/core": "^1.1.
|
|
3062
|
+
"@getstrata/bootstrap": "^1.1.6",
|
|
3063
|
+
"@getstrata/cli": "^1.1.6",
|
|
3064
|
+
"@getstrata/core": "^1.1.6"
|
|
2972
3065
|
};
|
|
2973
3066
|
coreDeps.eta = "^4.6.0";
|
|
2974
3067
|
if (options.layers?.database === "mysql") {
|
|
@@ -3092,6 +3185,9 @@ function renderApiDocs(projectName, layers) {
|
|
|
3092
3185
|
if (layers.extras.oauthGithub && authUsesCookie(layers.auth)) {
|
|
3093
3186
|
rows.push("| `GET` | `/auth/github` | Redirects to GitHub. 404 unless `FEATURE_OAUTH=true`. |", "| `GET` | `/auth/github/callback` | Exchanges the code, JIT user, MFA gate, cookie session. |");
|
|
3094
3187
|
}
|
|
3188
|
+
if (layers.extras.oidc && authUsesCookie(layers.auth)) {
|
|
3189
|
+
rows.push("| `GET` | `/auth/oidc` | OIDC `createAuthorization()` plus `oidc_pkce` cookie. 404 unless `FEATURE_OAUTH=true`. |", "| `GET` | `/auth/oidc/callback` | Reads PKCE, exchanges the code, JIT user, MFA gate, cookie session. |");
|
|
3190
|
+
}
|
|
3095
3191
|
const authNote = layers.auth === "headers" ? `Auth is \`headers\`. Send \`x-authenticated-user-id\` (and optional \`x-authenticated-user-role\`) for local work and tests. There are no login endpoints and no \`users\` table. Production must set \`AUTH_DEV_HEADERS=false\`, which turns those headers off and leaves you without a guard, so pick another auth layer before you ship.` : authUsesToken(layers.auth) ? `Sign in with \`GET /api/v1/auth/csrf\` then \`POST /api/v1/auth/login\` (send \`X-CSRF-Token\`), then send \`Authorization: Bearer <token>\` on every request. Tokens are stored hashed in \`api_tokens\` and expire after \`API_TOKEN_DEFAULT_EXPIRY_DAYS\` (30 in \`.env.example\`). The response includes \`expires_at\`.` : `Mint a JWT with \`POST /api/auth/token\`, then send \`Authorization: Bearer <jwt>\`. JWTs expire; re-mint rather than refreshing in place.`;
|
|
3096
3192
|
return `# ${projectName} API
|
|
3097
3193
|
|
|
@@ -3146,6 +3242,7 @@ Import from \`@getstrata/core/...\` subpaths rather than the package root, so si
|
|
|
3146
3242
|
function renderIntegrationsReadme(layers) {
|
|
3147
3243
|
const enabled = [
|
|
3148
3244
|
layers.extras.oauthGithub ? "GitHub OAuth cookie login (`/auth/github`)" : null,
|
|
3245
|
+
layers.extras.oidc ? "OIDC cookie login (`/auth/oidc`, PKCE in `oidc_pkce`)" : null,
|
|
3149
3246
|
layers.extras.billing ? "Stripe billing stub (`POST /billing/webhooks/stripe`, `GET /api/v1/billing/subscription`)" : null,
|
|
3150
3247
|
layers.extras.webhooks ? "Outbound webhooks (`notes.created` listener + `webhook.dispatch` job)" : null
|
|
3151
3248
|
].filter((row) => row !== null);
|
|
@@ -3154,7 +3251,7 @@ function renderIntegrationsReadme(layers) {
|
|
|
3154
3251
|
"",
|
|
3155
3252
|
"OAuth, billing, outbound webhooks, SIEM, and SAML are **off by default**. Commented placeholders live in `.env.example`. Framework map: [INTEGRATIONS.md](https://github.com/EyK-26/strata/blob/main/docs/INTEGRATIONS.md).",
|
|
3156
3253
|
"",
|
|
3157
|
-
"Wizard flags (any stack unless noted): `--oauth-github` (cookie HTML), `--billing`, `--webhooks`. They emit modules, migrations, jobs, or listeners. `createApp` still calls `discoverListeners()` and `discoverJobs()`.",
|
|
3254
|
+
"Wizard flags (any stack unless noted): `--oauth-github` and `--oidc` (cookie HTML), `--billing`, `--webhooks`. They emit modules, migrations, jobs, or listeners. `createApp` still calls `discoverListeners()` and `discoverJobs()`.",
|
|
3158
3255
|
""
|
|
3159
3256
|
];
|
|
3160
3257
|
if (enabled.length > 0) {
|
|
@@ -3255,7 +3352,7 @@ HTML auth kit (restyle \`views/\` and \`public/assets/site.css\`):
|
|
|
3255
3352
|
- Register: \`/register\`
|
|
3256
3353
|
- Forgot password: \`/forgot-password\`
|
|
3257
3354
|
- Reset password: signed \`/reset-password\` (mail log when \`MAIL_DRIVER=log\`)
|
|
3258
|
-
${layers.extras.emailVerification ? "- Verify email: `/email/verify`\n" : ""}${layers.extras.mfa ? "- MFA challenge: `/login/mfa` and setup: `/account/mfa`\n" : ""}${layers.extras.oauthGithub ? "- GitHub: `/auth/github` (needs `FEATURE_OAUTH=true`)\n" : ""}
|
|
3355
|
+
${layers.extras.emailVerification ? "- Verify email: `/email/verify`\n" : ""}${layers.extras.mfa ? "- MFA challenge: `/login/mfa` and setup: `/account/mfa`\n" : ""}${layers.extras.oauthGithub ? "- GitHub: `/auth/github` (needs `FEATURE_OAUTH=true`)\n" : ""}${layers.extras.oidc ? "- OIDC: `/auth/oidc` (needs `FEATURE_OAUTH=true`; PKCE cookie `oidc_pkce`)\n" : ""}
|
|
3259
3356
|
Cookie name is \`strata_session\`. Forms send CSRF as \`_token\`.
|
|
3260
3357
|
` : ""}${authUsesToken(layers.auth) ? `
|
|
3261
3358
|
Opaque token login: \`POST /api/v1/auth/login\` with \`{ "email", "password" }\`. Register: \`POST /api/v1/auth/register\`. Forgot/reset: \`POST /api/v1/auth/forgot-password\` and signed \`POST /api/v1/auth/reset-password\`. Send \`Authorization: Bearer\` after login.
|
|
@@ -3302,7 +3399,7 @@ The image sets \`APP_ENV=production\` and \`AUTH_DEV_HEADERS=false\`; everything
|
|
|
3302
3399
|
- Set \`FEATURE_PUBLIC_READS=false\`. Generated \`.env.example\` already ships \`false\` so \`wrapWebPublicRead\` requires a login. Local storefronts may set \`true\` in \`.env\` for an anonymous catalog; \`assertProductionSecrets()\` rejects \`true\` in production. Do not ship a public-reads production boot.
|
|
3303
3400
|
- Cross-origin browser calls are off in production until you set \`CORS_ALLOWED_ORIGINS\` to explicit origins. A \`*\` entry is rejected. Non-browser clients are unaffected.
|
|
3304
3401
|
- Behind a reverse proxy or load balancer, set \`TRUST_FORWARDED_FOR=true\` so throttles and session records see the client address instead of the proxy. Only the rightmost public hop of \`X-Forwarded-For\` is trusted.
|
|
3305
|
-
${authUsesCookie(layers.auth) ? "- Set `SESSION_SECRET` to 32+ characters.\n" : ""}${authUsesToken(layers.auth) ? "- Set `TOKEN_HASH_PEPPER`.\n" : ""}${layers.extras.scim ? "- Set `SCIM_BEARER_TOKEN`.\n" : ""}${layers.extras.metrics ? "- Set `METRICS_TOKEN`.\n" : ""}${layers.extras.oauthGithub ? "- Set `OAUTH_STATE_SECRET` and `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`.\n" : ""}${layers.extras.billing ? "- Set `STRIPE_WEBHOOK_SECRET` when `FEATURE_BILLING=true`.\n" : ""}${layers.tenancy === "rls" ? `- \`DATABASE_URL\` must be a \`NOBYPASSRLS\` role, not the \`postgres\` superuser. Generated apps create \`${GENERATED_POSTGRES_APP_ROLE}\` via \`db/ensure-postgres-app-role.sql\` (and Compose init on first empty volume). \`MIGRATION_DATABASE_URL\` may stay the superuser for CREATE ROLE / GRANT / migrate. Production boot rejects username \`postgres\` or \`root\`, then inspects \`pg_roles\` for \`rolsuper\` / \`rolbypassrls\`.
|
|
3402
|
+
${authUsesCookie(layers.auth) ? "- Set `SESSION_SECRET` to 32+ characters.\n" : ""}${authUsesToken(layers.auth) ? "- Set `TOKEN_HASH_PEPPER`.\n" : ""}${layers.extras.scim ? "- Set `SCIM_BEARER_TOKEN`.\n" : ""}${layers.extras.metrics ? "- Set `METRICS_TOKEN`.\n" : ""}${layers.extras.oauthGithub ? "- Set `OAUTH_STATE_SECRET` and `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`.\n" : ""}${layers.extras.oidc ? "- Set `OAUTH_STATE_SECRET`, `OIDC_ISSUER`, `OIDC_CLIENT_ID`, and `OIDC_CLIENT_SECRET`. Loopback issuers work outside production; set `OIDC_ALLOW_PRIVATE=true` for other private IdPs.\n" : ""}${layers.extras.billing ? "- Set `STRIPE_WEBHOOK_SECRET` when `FEATURE_BILLING=true`.\n" : ""}${layers.tenancy === "rls" ? `- \`DATABASE_URL\` must be a \`NOBYPASSRLS\` role, not the \`postgres\` superuser. Generated apps create \`${GENERATED_POSTGRES_APP_ROLE}\` via \`db/ensure-postgres-app-role.sql\` (and Compose init on first empty volume). \`MIGRATION_DATABASE_URL\` may stay the superuser for CREATE ROLE / GRANT / migrate. Production boot rejects username \`postgres\` or \`root\`, then inspects \`pg_roles\` for \`rolsuper\` / \`rolbypassrls\`.
|
|
3306
3403
|
` : ""}
|
|
3307
3404
|
\`strata start\` does not migrate when \`APP_ENV=production\`. Run \`bun run db:migrate\` as a deploy step. \`GET /health\` is 200 when \`notes\` is readable (including zero rows) and 503 when that read fails. Redis jobs need a worker: \`bun run queue:work\` (requires \`REDIS_URL\`).
|
|
3308
3405
|
`;
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"check": "tsc --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@getstrata/bootstrap": "^1.1.
|
|
16
|
-
"@getstrata/cli": "^1.1.
|
|
17
|
-
"@getstrata/core": "^1.1.
|
|
15
|
+
"@getstrata/bootstrap": "^1.1.6",
|
|
16
|
+
"@getstrata/cli": "^1.1.6",
|
|
17
|
+
"@getstrata/core": "^1.1.6",
|
|
18
18
|
"eta": "^4.6.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|