@aurostack/stacks 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  House project templates, and the generator that stamps them out.
4
4
 
5
+ **Documentation: [aurostack-org.github.io/stacks](https://aurostack-org.github.io/stacks/)**:
6
+ every template, feature and environment variable, how to set up and connect a
7
+ generated project, and architecture diagrams. Sources in [`docs/`](docs/).
8
+
5
9
  ```
6
10
  stack list # what's available
7
11
  stack info nest-api # its features and defaults
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurostack/stacks",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Scaffold new projects from house templates: a NestJS API, React app or Turborepo monorepo, and Node or Python workers. Subtractive templates plus a zero-dependency generator.",
5
5
  "license": "MIT",
6
6
  "author": "Aurostack (https://github.com/aurostack-org)",
@@ -8,7 +8,11 @@ APP_HOST='0.0.0.0'
8
8
  PORT='5000'
9
9
  SERVER_HOST='http://localhost:5000'
10
10
  FRONTEND_HOST='http://localhost:3000'
11
- # Extra CORS origins beyond FRONTEND_HOST, comma separated.
11
+ # Extra origins beyond FRONTEND_HOST, comma separated, trusted by auth and
12
+ # allowed by CORS: every react-monorepo app goes here or in FRONTEND_HOST. A full
13
+ # origin (`http://localhost:3001`) is used as written; a bare hostname
14
+ # (`example.com`) expands to https://example.com and its auth., app. and admin.
15
+ # subdomains.
12
16
  MISC_CORS_ORIGINS=''
13
17
 
14
18
  DATABASE_URL='postgresql://root:postgres@localhost:5432/main'
@@ -16,9 +20,13 @@ DATABASE_URL='postgresql://root:postgres@localhost:5432/main'
16
20
  # openssl rand -base64 32
17
21
  BETTER_AUTH_SECRET='change-me-openssl-rand-base64-32'
18
22
  BETTER_AUTH_URL='http://localhost:5000'
19
- # Leading dot shares the session cookie across subdomains. `.lvh.me` resolves
20
- # to 127.0.0.1 for every subdomain, which makes local multi-app auth work.
21
- BETTER_AUTH_COOKIE_DOMAIN='.lvh.me'
23
+ # Empty: the session cookie belongs to the API's own host, which is what
24
+ # localhost development and a single-origin frontend need. Set a parent domain
25
+ # with a leading dot (`.example.com`) when frontends live on subdomains, to share
26
+ # one login across them; locally, `.lvh.me` does the same with `*.lvh.me` hosts
27
+ # (every subdomain resolves to 127.0.0.1). The domain must cover the host the
28
+ # browser talks to, or the cookie is dropped and every sign-in bounces.
29
+ BETTER_AUTH_COOKIE_DOMAIN=''
22
30
  BETTER_AUTH_COOKIE_PREFIX='acme'
23
31
 
24
32
  # Seeded by `yarn db:seed`.
@@ -81,13 +81,15 @@ import { OriginBuilder } from 'common/misc';
81
81
  ipAddressHeaders: config.rateLimit.ipHeaders
82
82
  },
83
83
  // @feature:end
84
- // Share the session cookie across subdomains so one login covers
85
- // every frontend. Domain comes from BETTER_AUTH_COOKIE_DOMAIN
86
- // (`.lvh.me` in dev, your apex in production).
84
+ // With BETTER_AUTH_COOKIE_DOMAIN set (`.example.com`), the session
85
+ // cookie is shared across subdomains so one login covers every
86
+ // frontend. Empty, it belongs to the API's own host: right for
87
+ // localhost and for a single-origin frontend. A browser drops a
88
+ // cookie whose domain doesn't cover the host that set it.
87
89
  cookiePrefix: config.betterAuth.cookiePrefix,
88
90
  crossSubDomainCookies: {
89
- enabled: true,
90
- domain: config.betterAuth.cookieDomain
91
+ enabled: Boolean(config.betterAuth.cookieDomain),
92
+ domain: config.betterAuth.cookieDomain || undefined
91
93
  },
92
94
  database: {
93
95
  joins: true
@@ -0,0 +1,24 @@
1
+ import { OriginBuilder } from 'common/misc/origin-builder';
2
+
3
+ describe('OriginBuilder', () => {
4
+ it('expands a bare hostname into https origins for it and its subdomains', () => {
5
+ expect(OriginBuilder.build('example.com')).toEqual([
6
+ 'https://example.com',
7
+ 'https://auth.example.com',
8
+ 'https://app.example.com',
9
+ 'https://admin.example.com'
10
+ ]);
11
+ });
12
+
13
+ it('takes an entry with a scheme as one origin, as written', () => {
14
+ expect(OriginBuilder.build('http://localhost:3001, http://localhost:3002/')).toEqual([
15
+ 'http://localhost:3001',
16
+ 'http://localhost:3002'
17
+ ]);
18
+ });
19
+
20
+ it('is empty for an empty list', () => {
21
+ expect(OriginBuilder.build('')).toEqual([]);
22
+ expect(OriginBuilder.build(undefined)).toEqual([]);
23
+ });
24
+ });
@@ -14,11 +14,11 @@ const frontendHost = E.FRONTEND_HOST!;
14
14
  const env = E.APP_ENV!;
15
15
  const IS_DEV = env === 'development';
16
16
 
17
- // Extra origins are a development convenience only — production is pinned to
18
- // the configured frontend host.
19
- const CORS_ORIGINS = !IS_DEV
20
- ? [frontendHost]
21
- : [frontendHost, ...OriginBuilder.build(E.MISC_CORS_ORIGINS)];
17
+ // The same origins better-auth trusts, in every environment: an origin trusted
18
+ // for sign-in but refused by CORS can't reach the API at all (every
19
+ // react-monorepo app beyond FRONTEND_HOST). MISC_CORS_ORIGINS is empty unless
20
+ // you list something.
21
+ const CORS_ORIGINS = [frontendHost, ...OriginBuilder.build(E.MISC_CORS_ORIGINS)];
22
22
 
23
23
  // @feature:start openapi
24
24
  const createDocumentBuilder = () => {
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Builds a list of origins for CORS configuration, given a list of hostnames.
3
- * Each hostname will generate the main domain and subdomains
4
- * for app, auth, and admin.
2
+ * Builds a list of origins for CORS configuration from a list of entries.
3
+ * A bare hostname (`shop.example.com`) generates https origins for it and its
4
+ * app, auth and admin subdomains. An entry with a scheme
5
+ * (`http://localhost:3001`) is taken as one origin, as written: that is how
6
+ * frontends on other local ports are allowed.
5
7
  *
6
8
  * @remarks This is mainly for development purposes
7
9
  * For frontend developers on the team who cannot run a standalone
@@ -28,6 +30,7 @@ export class OriginBuilder {
28
30
  * Origins for a single hostname: the main domain plus each subdomain.
29
31
  */
30
32
  fromHost(hostname: string): string[] {
33
+ if (hostname.includes('://')) return [hostname.replace(/\/+$/, '')];
31
34
  return [
32
35
  `${this.protocol}${hostname}`,
33
36
  ...this.subdomains.map((sub) => `${this.protocol}${sub}.${hostname}`)
@@ -172,7 +172,7 @@ namespace Config {
172
172
  DATABASE_URL: str(),
173
173
  BETTER_AUTH_SECRET: str(),
174
174
  BETTER_AUTH_URL: str(),
175
- BETTER_AUTH_COOKIE_DOMAIN: str(),
175
+ BETTER_AUTH_COOKIE_DOMAIN: z.string().default(''),
176
176
  BETTER_AUTH_COOKIE_PREFIX: str(),
177
177
  SUPERUSER_EMAIL: str(),
178
178
  SUPERUSER_PASSWORD: str(),
@@ -33,8 +33,8 @@ export const auth = betterAuth({
33
33
  // sessions through this one, so a different cookie name would reject them.
34
34
  cookiePrefix: env.BETTER_AUTH_COOKIE_PREFIX,
35
35
  crossSubDomainCookies: {
36
- enabled: true,
37
- domain: env.BETTER_AUTH_COOKIE_DOMAIN
36
+ enabled: Boolean(env.BETTER_AUTH_COOKIE_DOMAIN),
37
+ domain: env.BETTER_AUTH_COOKIE_DOMAIN || undefined
38
38
  },
39
39
  database: {
40
40
  joins: true