@augmenting-integrations/create-tenant 0.0.0
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.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +77 -0
- package/dist/cli.js.map +1 -0
- package/package.json +29 -0
- package/templates/.env.example.tmpl +22 -0
- package/templates/.gitignore.tmpl +7 -0
- package/templates/README.md.tmpl +44 -0
- package/templates/next.config.ts.tmpl +8 -0
- package/templates/package.json.tmpl +31 -0
- package/templates/src/app/Providers.tsx.tmpl +25 -0
- package/templates/src/app/api/apps/route.ts.tmpl +7 -0
- package/templates/src/app/api/auth/[...nextauth]/route.ts.tmpl +6 -0
- package/templates/src/app/globals.css.tmpl +22 -0
- package/templates/src/app/layout.tsx.tmpl +42 -0
- package/templates/src/app/login/page.tsx.tmpl +26 -0
- package/templates/src/app/page.tsx.tmpl +14 -0
- package/templates/src/lib/auth.ts.tmpl +20 -0
- package/templates/src/proxy.ts.tmpl +7 -0
- package/templates/tsconfig.json.tmpl +21 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import process from "process";
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
const args = argv.slice(2);
|
|
10
|
+
let name;
|
|
11
|
+
let apex;
|
|
12
|
+
for (const a of args) {
|
|
13
|
+
if (a.startsWith("--apex=")) apex = a.slice("--apex=".length);
|
|
14
|
+
else if (!a.startsWith("--")) name = a;
|
|
15
|
+
}
|
|
16
|
+
if (!name) {
|
|
17
|
+
console.error("Usage: create-tenant <name> [--apex=tenant.example.com]");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
return { name, apex: apex ?? `${name}.example.com` };
|
|
21
|
+
}
|
|
22
|
+
function templatesDir() {
|
|
23
|
+
const here = fileURLToPath(import.meta.url);
|
|
24
|
+
return path.resolve(path.dirname(here), "..", "templates");
|
|
25
|
+
}
|
|
26
|
+
function ensureEmptyDir(target) {
|
|
27
|
+
if (fs.existsSync(target)) {
|
|
28
|
+
const entries = fs.readdirSync(target);
|
|
29
|
+
if (entries.length > 0) {
|
|
30
|
+
console.error(`Refusing to write into non-empty directory: ${target}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
fs.mkdirSync(target, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function replaceVars(content, flags) {
|
|
38
|
+
return content.replace(/__TENANT_NAME__/g, flags.name).replace(/__TENANT_APEX__/g, flags.apex).replace(/__TENANT_PARENT__/g, `.${flags.apex}`);
|
|
39
|
+
}
|
|
40
|
+
function copyTree(src, dst, flags) {
|
|
41
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
42
|
+
const srcPath = path.join(src, entry.name);
|
|
43
|
+
const baseName = entry.name.endsWith(".tmpl") ? entry.name.slice(0, -".tmpl".length) : entry.name;
|
|
44
|
+
const dstPath = path.join(dst, replaceVars(baseName, flags));
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
fs.mkdirSync(dstPath, { recursive: true });
|
|
47
|
+
copyTree(srcPath, dstPath, flags);
|
|
48
|
+
} else {
|
|
49
|
+
const raw = fs.readFileSync(srcPath, "utf8");
|
|
50
|
+
fs.writeFileSync(dstPath, replaceVars(raw, flags), "utf8");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function main() {
|
|
55
|
+
const flags = parseArgs(process.argv);
|
|
56
|
+
const target = path.resolve(process.cwd(), flags.name);
|
|
57
|
+
ensureEmptyDir(target);
|
|
58
|
+
const templates = templatesDir();
|
|
59
|
+
if (!fs.existsSync(templates)) {
|
|
60
|
+
console.error(`Templates directory missing at ${templates}. Re-install the CLI.`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
copyTree(templates, target, flags);
|
|
64
|
+
console.log(`Created apex "${flags.name}" at ${target}`);
|
|
65
|
+
console.log(`Apex domain: ${flags.apex}`);
|
|
66
|
+
console.log("");
|
|
67
|
+
console.log("Next steps:");
|
|
68
|
+
console.log(` cd ${flags.name}`);
|
|
69
|
+
console.log(" cp .env.example .env # fill in Cognito ARNs + AWS resources");
|
|
70
|
+
console.log(" pnpm install");
|
|
71
|
+
console.log(" pnpm dev");
|
|
72
|
+
console.log("");
|
|
73
|
+
console.log("Then provision tenant infra (Cognito + DNS + registry table):");
|
|
74
|
+
console.log(" see README.md");
|
|
75
|
+
}
|
|
76
|
+
main();
|
|
77
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport process from \"node:process\";\n\n// =============================================================================\n// create-tenant -- scaffold a new tenant apex app.\n//\n// Usage:\n// pnpm dlx @augmenting-integrations/create-tenant <name> [--apex=foo.com]\n//\n// Writes a minimal but deployable Next 16 apex to ./<name>/ that:\n// - Mounts /api/auth/[...nextauth] -- the ONLY Cognito OAuth callback in\n// the tenant ecosystem\n// - Exposes /api/apps (registry auto-discovery, read by every spoke's\n// AppShell) and /api/admin/apps (admin-only CRUD)\n// - Wires loadTenantConfig({ role: \"apex\" }) + TenantBootScript +\n// TenantProvider so spokes can read the same tenant struct\n// - Includes a /studio admin landing page stub\n//\n// Out of scope (manual follow-up, documented in the generated README):\n// - Cognito User Pool + App Client provisioning\n// - DNS hosted zone + cert\n// - App registry DynamoDB table\n// - GitHub OIDC role for deploys\n// - First spoke (use create-spoke for each product subdomain)\n// =============================================================================\n\ntype Flags = {\n name: string;\n apex: string;\n};\n\nfunction parseArgs(argv: string[]): Flags {\n const args = argv.slice(2);\n let name: string | undefined;\n let apex: string | undefined;\n for (const a of args) {\n if (a.startsWith(\"--apex=\")) apex = a.slice(\"--apex=\".length);\n else if (!a.startsWith(\"--\")) name = a;\n }\n if (!name) {\n console.error(\"Usage: create-tenant <name> [--apex=tenant.example.com]\");\n process.exit(1);\n }\n return { name, apex: apex ?? `${name}.example.com` };\n}\n\nfunction templatesDir(): string {\n const here = fileURLToPath(import.meta.url);\n return path.resolve(path.dirname(here), \"..\", \"templates\");\n}\n\nfunction ensureEmptyDir(target: string): void {\n if (fs.existsSync(target)) {\n const entries = fs.readdirSync(target);\n if (entries.length > 0) {\n console.error(`Refusing to write into non-empty directory: ${target}`);\n process.exit(1);\n }\n } else {\n fs.mkdirSync(target, { recursive: true });\n }\n}\n\nfunction replaceVars(content: string, flags: Flags): string {\n return content\n .replace(/__TENANT_NAME__/g, flags.name)\n .replace(/__TENANT_APEX__/g, flags.apex)\n .replace(/__TENANT_PARENT__/g, `.${flags.apex}`);\n}\n\nfunction copyTree(src: string, dst: string, flags: Flags): void {\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n const srcPath = path.join(src, entry.name);\n const baseName = entry.name.endsWith(\".tmpl\")\n ? entry.name.slice(0, -\".tmpl\".length)\n : entry.name;\n const dstPath = path.join(dst, replaceVars(baseName, flags));\n if (entry.isDirectory()) {\n fs.mkdirSync(dstPath, { recursive: true });\n copyTree(srcPath, dstPath, flags);\n } else {\n const raw = fs.readFileSync(srcPath, \"utf8\");\n fs.writeFileSync(dstPath, replaceVars(raw, flags), \"utf8\");\n }\n }\n}\n\nfunction main(): void {\n const flags = parseArgs(process.argv);\n const target = path.resolve(process.cwd(), flags.name);\n ensureEmptyDir(target);\n const templates = templatesDir();\n if (!fs.existsSync(templates)) {\n console.error(`Templates directory missing at ${templates}. Re-install the CLI.`);\n process.exit(1);\n }\n copyTree(templates, target, flags);\n console.log(`Created apex \"${flags.name}\" at ${target}`);\n console.log(`Apex domain: ${flags.apex}`);\n console.log(\"\");\n console.log(\"Next steps:\");\n console.log(` cd ${flags.name}`);\n console.log(\" cp .env.example .env # fill in Cognito ARNs + AWS resources\");\n console.log(\" pnpm install\");\n console.log(\" pnpm dev\");\n console.log(\"\");\n console.log(\"Then provision tenant infra (Cognito + DNS + registry table):\");\n console.log(\" see README.md\");\n}\n\nmain();\n"],"mappings":";;;AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,SAAS,qBAAqB;AAC9B,OAAO,aAAa;AA8BpB,SAAS,UAAU,MAAuB;AACxC,QAAM,OAAO,KAAK,MAAM,CAAC;AACzB,MAAI;AACJ,MAAI;AACJ,aAAW,KAAK,MAAM;AACpB,QAAI,EAAE,WAAW,SAAS,EAAG,QAAO,EAAE,MAAM,UAAU,MAAM;AAAA,aACnD,CAAC,EAAE,WAAW,IAAI,EAAG,QAAO;AAAA,EACvC;AACA,MAAI,CAAC,MAAM;AACT,YAAQ,MAAM,yDAAyD;AACvE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO,EAAE,MAAM,MAAM,QAAQ,GAAG,IAAI,eAAe;AACrD;AAEA,SAAS,eAAuB;AAC9B,QAAM,OAAO,cAAc,YAAY,GAAG;AAC1C,SAAY,aAAa,aAAQ,IAAI,GAAG,MAAM,WAAW;AAC3D;AAEA,SAAS,eAAe,QAAsB;AAC5C,MAAO,cAAW,MAAM,GAAG;AACzB,UAAM,UAAa,eAAY,MAAM;AACrC,QAAI,QAAQ,SAAS,GAAG;AACtB,cAAQ,MAAM,+CAA+C,MAAM,EAAE;AACrE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,OAAO;AACL,IAAG,aAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AAAA,EAC1C;AACF;AAEA,SAAS,YAAY,SAAiB,OAAsB;AAC1D,SAAO,QACJ,QAAQ,oBAAoB,MAAM,IAAI,EACtC,QAAQ,oBAAoB,MAAM,IAAI,EACtC,QAAQ,sBAAsB,IAAI,MAAM,IAAI,EAAE;AACnD;AAEA,SAAS,SAAS,KAAa,KAAa,OAAoB;AAC9D,aAAW,SAAY,eAAY,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;AAChE,UAAM,UAAe,UAAK,KAAK,MAAM,IAAI;AACzC,UAAM,WAAW,MAAM,KAAK,SAAS,OAAO,IACxC,MAAM,KAAK,MAAM,GAAG,CAAC,QAAQ,MAAM,IACnC,MAAM;AACV,UAAM,UAAe,UAAK,KAAK,YAAY,UAAU,KAAK,CAAC;AAC3D,QAAI,MAAM,YAAY,GAAG;AACvB,MAAG,aAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AACzC,eAAS,SAAS,SAAS,KAAK;AAAA,IAClC,OAAO;AACL,YAAM,MAAS,gBAAa,SAAS,MAAM;AAC3C,MAAG,iBAAc,SAAS,YAAY,KAAK,KAAK,GAAG,MAAM;AAAA,IAC3D;AAAA,EACF;AACF;AAEA,SAAS,OAAa;AACpB,QAAM,QAAQ,UAAU,QAAQ,IAAI;AACpC,QAAM,SAAc,aAAQ,QAAQ,IAAI,GAAG,MAAM,IAAI;AACrD,iBAAe,MAAM;AACrB,QAAM,YAAY,aAAa;AAC/B,MAAI,CAAI,cAAW,SAAS,GAAG;AAC7B,YAAQ,MAAM,kCAAkC,SAAS,uBAAuB;AAChF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,WAAS,WAAW,QAAQ,KAAK;AACjC,UAAQ,IAAI,iBAAiB,MAAM,IAAI,QAAQ,MAAM,EAAE;AACvD,UAAQ,IAAI,gBAAgB,MAAM,IAAI,EAAE;AACxC,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,aAAa;AACzB,UAAQ,IAAI,QAAQ,MAAM,IAAI,EAAE;AAChC,UAAQ,IAAI,gEAAgE;AAC5E,UAAQ,IAAI,gBAAgB;AAC5B,UAAQ,IAAI,YAAY;AACxB,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,+DAA+D;AAC3E,UAAQ,IAAI,iBAAiB;AAC/B;AAEA,KAAK;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@augmenting-integrations/create-tenant",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Scaffold a new tenant apex app for an augint deployment. Generates a Next 16 + Auth.js v5 + Cognito apex with TenantConfig wired up, library-owned /api/apps registry handler, and /studio admin. Single command: pnpm dlx @augmenting-integrations/create-tenant my-tenant.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"create-tenant": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"templates",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"clean": "rm -rf dist",
|
|
21
|
+
"test": "vitest run --passWithNoTests"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^22.0.0",
|
|
25
|
+
"tsup": "^8.3.5",
|
|
26
|
+
"typescript": "^5.7.2",
|
|
27
|
+
"vitest": "^4.1.5"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
APP_DOMAIN=__TENANT_APEX__
|
|
2
|
+
APEX_DOMAIN=__TENANT_APEX__
|
|
3
|
+
AUTH_COOKIE_DOMAIN=__TENANT_PARENT__
|
|
4
|
+
AUTH_ALLOWED_PARENT_DOMAIN=__TENANT_PARENT__
|
|
5
|
+
|
|
6
|
+
# Cognito (apex-only; from your tenant infra stack)
|
|
7
|
+
AUTH_SECRET_ARN=
|
|
8
|
+
AUTH_COGNITO_SECRET_ARN=
|
|
9
|
+
AUTH_COGNITO_ID=
|
|
10
|
+
AUTH_COGNITO_ISSUER=
|
|
11
|
+
|
|
12
|
+
# App registry DynamoDB table (apex owns CRUD; spokes read)
|
|
13
|
+
APP_REGISTRY_TABLE=
|
|
14
|
+
TENANT_SLUG=__TENANT_NAME__
|
|
15
|
+
STAGE=staging
|
|
16
|
+
|
|
17
|
+
AWS_REGION=us-east-1
|
|
18
|
+
ADMIN_EMAILS=
|
|
19
|
+
|
|
20
|
+
# Local-dev fallbacks
|
|
21
|
+
AUTH_SECRET=dev-only-fallback-not-for-prod
|
|
22
|
+
NODE_ENV=development
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# __TENANT_NAME__ apex
|
|
2
|
+
|
|
3
|
+
Generated by `@augmenting-integrations/create-tenant`. This is the apex
|
|
4
|
+
Next.js 16 app for a new augint tenant. It owns:
|
|
5
|
+
|
|
6
|
+
- The OAuth callback (`/api/auth/[...nextauth]`) for the ENTIRE tenant
|
|
7
|
+
- The session cookie scope (`Domain=__TENANT_PARENT__`)
|
|
8
|
+
- The app registry (`/api/apps` for spoke auto-discovery)
|
|
9
|
+
- The studio admin
|
|
10
|
+
|
|
11
|
+
## Local dev
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cp .env.example .env
|
|
15
|
+
# fill in tenant identity values; AWS resources can be blank for local dev
|
|
16
|
+
pnpm install
|
|
17
|
+
pnpm dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Per-tenant infra (NOT scaffolded — provision separately)
|
|
21
|
+
|
|
22
|
+
The application code is portable; the AWS infra is tenant-specific:
|
|
23
|
+
|
|
24
|
+
1. **AWS account** — sandbox/staging + prod accounts.
|
|
25
|
+
2. **Cognito User Pool** with one App Client + ONE callback URL:
|
|
26
|
+
`https://__TENANT_APEX__/api/auth/callback/cognito`
|
|
27
|
+
3. **Hosted zone + certs** for `__TENANT_APEX__` and `*.__TENANT_APEX__`.
|
|
28
|
+
4. **App registry DynamoDB table** (PK = `slug`). Apex owns CRUD; spokes
|
|
29
|
+
read via `/api/apps`. Set `APP_REGISTRY_TABLE` env to the table name.
|
|
30
|
+
5. **Secrets Manager** rows for `AUTH_SECRET`, `AUTH_COGNITO_SECRET`.
|
|
31
|
+
6. **GitHub OIDC role** in each AWS account for CI deploys.
|
|
32
|
+
|
|
33
|
+
Copy `template.yaml` from an existing tenant (the example tenant ships one)
|
|
34
|
+
and adapt the parameters.
|
|
35
|
+
|
|
36
|
+
## Adding spokes
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm dlx @augmenting-integrations/create-spoke my-product-spoke
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then register the new spoke in this apex's app registry table (slug,
|
|
43
|
+
subdomain, displayName, navOrder). The spoke shows up in every other
|
|
44
|
+
spoke's AppShell ecosystem nav automatically.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__TENANT_NAME__-apex",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"type-check": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@augmenting-integrations/auth": "^8.0.0",
|
|
15
|
+
"@augmenting-integrations/aws": "^8.0.0",
|
|
16
|
+
"@augmenting-integrations/brand": "^8.0.0",
|
|
17
|
+
"@augmenting-integrations/registry": "^8.0.0",
|
|
18
|
+
"@augmenting-integrations/themes": "^8.0.0",
|
|
19
|
+
"@augmenting-integrations/ui": "^8.0.0",
|
|
20
|
+
"next": "^16.2.5",
|
|
21
|
+
"next-auth": "^5.0.0-beta.31",
|
|
22
|
+
"react": "^19.2.0",
|
|
23
|
+
"react-dom": "^19.2.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.0.0",
|
|
27
|
+
"@types/react": "^19.0.0",
|
|
28
|
+
"@types/react-dom": "^19.0.0",
|
|
29
|
+
"typescript": "^5.7.2"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import type { Session } from "next-auth";
|
|
5
|
+
import { SessionProvider } from "@augmenting-integrations/ui";
|
|
6
|
+
import {
|
|
7
|
+
TenantProvider,
|
|
8
|
+
type TenantPublicConfig,
|
|
9
|
+
} from "@augmenting-integrations/auth/client";
|
|
10
|
+
|
|
11
|
+
export function Providers({
|
|
12
|
+
children,
|
|
13
|
+
session,
|
|
14
|
+
tenant,
|
|
15
|
+
}: {
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
session: Session | null;
|
|
18
|
+
tenant: TenantPublicConfig;
|
|
19
|
+
}) {
|
|
20
|
+
return (
|
|
21
|
+
<TenantProvider tenant={tenant}>
|
|
22
|
+
<SessionProvider session={session}>{children}</SessionProvider>
|
|
23
|
+
</TenantProvider>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Auto-discovery endpoint consumed by every spoke's AppShell. Scans the
|
|
2
|
+
// DynamoDB app registry table + filters by the caller's Cognito groups.
|
|
3
|
+
import { createGetHandler } from "@augmenting-integrations/registry/api-route";
|
|
4
|
+
import { auth } from "@/lib/auth";
|
|
5
|
+
|
|
6
|
+
export const GET = createGetHandler({ authFn: auth });
|
|
7
|
+
export const runtime = "nodejs";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The ONLY Cognito callback in the tenant ecosystem. Subdomain spokes
|
|
2
|
+
// redirect to /login here; we run the OAuth dance and set the parent-domain
|
|
3
|
+
// cookie so every spoke sees the session on the next request.
|
|
4
|
+
import { handlers } from "@/lib/auth";
|
|
5
|
+
|
|
6
|
+
export const { GET, POST } = handlers;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@source "../../node_modules/@augmenting-integrations/ui/dist";
|
|
4
|
+
@source "../../node_modules/@augmenting-integrations/auth/dist";
|
|
5
|
+
@source "../../node_modules/@augmenting-integrations/brand/dist";
|
|
6
|
+
|
|
7
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
--background: oklch(0.99 0 0);
|
|
11
|
+
--foreground: oklch(0.15 0 0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.dark {
|
|
15
|
+
--background: oklch(0.12 0.02 240);
|
|
16
|
+
--foreground: oklch(0.95 0 0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
background-color: var(--background);
|
|
21
|
+
color: var(--foreground);
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { cookies } from "next/headers";
|
|
3
|
+
import {
|
|
4
|
+
THEME_COOKIE_KEY,
|
|
5
|
+
THEME_VARIANT_COOKIE_KEY,
|
|
6
|
+
} from "@augmenting-integrations/themes";
|
|
7
|
+
import { ThemeBootScript } from "@augmenting-integrations/ui";
|
|
8
|
+
import { TenantBootScript, publicSubset } from "@augmenting-integrations/auth/server";
|
|
9
|
+
import { auth, tenant } from "@/lib/auth";
|
|
10
|
+
import { Providers } from "./Providers";
|
|
11
|
+
import "./globals.css";
|
|
12
|
+
|
|
13
|
+
export const metadata: Metadata = {
|
|
14
|
+
title: { default: "__TENANT_NAME__", template: "%s — __TENANT_NAME__" },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
18
|
+
const [session, cookieStore] = await Promise.all([auth(), cookies()]);
|
|
19
|
+
const themeName = cookieStore.get(THEME_COOKIE_KEY)?.value ?? "default";
|
|
20
|
+
const variantCookie = cookieStore.get(THEME_VARIANT_COOKIE_KEY)?.value;
|
|
21
|
+
const variant: "dark" | "light" =
|
|
22
|
+
variantCookie === "dark" || variantCookie === "light" ? variantCookie : "light";
|
|
23
|
+
const publicTenant = publicSubset(tenant);
|
|
24
|
+
return (
|
|
25
|
+
<html
|
|
26
|
+
lang="en"
|
|
27
|
+
data-theme={themeName}
|
|
28
|
+
className={variant === "dark" ? "dark" : undefined}
|
|
29
|
+
suppressHydrationWarning
|
|
30
|
+
>
|
|
31
|
+
<head>
|
|
32
|
+
<TenantBootScript config={publicTenant} />
|
|
33
|
+
<ThemeBootScript />
|
|
34
|
+
</head>
|
|
35
|
+
<body>
|
|
36
|
+
<Providers session={session} tenant={publicTenant}>
|
|
37
|
+
{children}
|
|
38
|
+
</Providers>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { signIn } from "@/lib/auth";
|
|
2
|
+
|
|
3
|
+
export default function LoginPage() {
|
|
4
|
+
return (
|
|
5
|
+
<main
|
|
6
|
+
style={{
|
|
7
|
+
padding: "2rem",
|
|
8
|
+
fontFamily: "system-ui",
|
|
9
|
+
display: "flex",
|
|
10
|
+
flexDirection: "column",
|
|
11
|
+
alignItems: "center",
|
|
12
|
+
gap: "1rem",
|
|
13
|
+
}}
|
|
14
|
+
>
|
|
15
|
+
<h1>Sign in to __TENANT_NAME__</h1>
|
|
16
|
+
<form
|
|
17
|
+
action={async () => {
|
|
18
|
+
"use server";
|
|
19
|
+
await signIn("cognito", { redirectTo: "/home" });
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
<button type="submit">Sign in with Cognito</button>
|
|
23
|
+
</form>
|
|
24
|
+
</main>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
return (
|
|
5
|
+
<main style={{ padding: "2rem", fontFamily: "system-ui" }}>
|
|
6
|
+
<h1>__TENANT_NAME__</h1>
|
|
7
|
+
<p>Tenant apex at <strong>__TENANT_APEX__</strong>.</p>
|
|
8
|
+
<p>
|
|
9
|
+
<Link href="/login">Sign in</Link> to access the studio admin or any
|
|
10
|
+
product subdomain.
|
|
11
|
+
</p>
|
|
12
|
+
</main>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createAuth, loadTenantConfig } from "@augmenting-integrations/auth/server";
|
|
2
|
+
import { getSecret } from "@augmenting-integrations/aws/server";
|
|
3
|
+
|
|
4
|
+
// Single tenant configuration source.
|
|
5
|
+
export const tenant = loadTenantConfig({ role: "apex" });
|
|
6
|
+
|
|
7
|
+
// Apex is the OAuth broker -- it needs both auth secret and Cognito client
|
|
8
|
+
// secret. Both live in Secrets Manager (ARNs in tenant config).
|
|
9
|
+
const [authSecretRaw, cognitoClientSecretRaw] = await Promise.all([
|
|
10
|
+
getSecret(tenant.authSecretArn),
|
|
11
|
+
getSecret(tenant.authCognitoSecretArn!),
|
|
12
|
+
]);
|
|
13
|
+
const authSecret = authSecretRaw ?? "dev-only-fallback-not-for-prod";
|
|
14
|
+
|
|
15
|
+
export const { handlers, auth, signIn, signOut } = createAuth({
|
|
16
|
+
tenant,
|
|
17
|
+
authedRoutePrefixes: ["/home", "/studio"],
|
|
18
|
+
authSecret,
|
|
19
|
+
cognitoClientSecret: cognitoClientSecretRaw,
|
|
20
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": false,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }],
|
|
17
|
+
"paths": { "@/*": ["./src/*"] }
|
|
18
|
+
},
|
|
19
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
20
|
+
"exclude": ["node_modules"]
|
|
21
|
+
}
|