@monster-orange/den 0.0.0 → 0.1.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.
Files changed (231) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/LICENSE +21 -0
  3. package/README.md +165 -1
  4. package/bin/den +61 -0
  5. package/docs/distributions.md +256 -0
  6. package/make/demo.mk +82 -0
  7. package/make/den.mk +80 -0
  8. package/make/journeys.mk +57 -0
  9. package/make/local_dev.mk +154 -0
  10. package/make/security.mk +59 -0
  11. package/make/test.mk +405 -0
  12. package/package.json +99 -4
  13. package/scripts/auth-configure.ts +415 -0
  14. package/scripts/heal-config.ts +92 -0
  15. package/scripts/jsonl-playwright-reporter.js +70 -0
  16. package/scripts/manifest-read.cjs +91 -0
  17. package/scripts/setup-1password.ts +570 -0
  18. package/scripts/setup-secrets.ts +149 -0
  19. package/scripts/view-results.js +156 -0
  20. package/service/install.sh +112 -0
  21. package/src/pillars/api/fixtures/index.ts +128 -0
  22. package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
  23. package/src/pillars/api/global-setup.ts +103 -0
  24. package/src/pillars/api/playwright.config.ts +105 -0
  25. package/src/pillars/api/schemas/example-api.json +110 -0
  26. package/src/pillars/api/tests/health/health.spec.ts +19 -0
  27. package/src/pillars/api/tests/health/manifest.yml +6 -0
  28. package/src/pillars/browser/fixtures/index.ts +232 -0
  29. package/src/pillars/browser/fixtures/package.json +10 -0
  30. package/src/pillars/browser/global-setup.ts +140 -0
  31. package/src/pillars/browser/global-teardown.ts +48 -0
  32. package/src/pillars/browser/playwright.config.ts +212 -0
  33. package/src/pillars/browser/setup.ts +219 -0
  34. package/src/pillars/journeys/app-config.ts +237 -0
  35. package/src/pillars/journeys/discover.ts +160 -0
  36. package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
  37. package/src/pillars/journeys/observability/events.ts +288 -0
  38. package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
  39. package/src/pillars/journeys/report/friction.ts +304 -0
  40. package/src/pillars/journeys/report/junit.ts +105 -0
  41. package/src/pillars/journeys/report/render.ts +273 -0
  42. package/src/pillars/journeys/report/results.ts +323 -0
  43. package/src/pillars/journeys/run.ts +673 -0
  44. package/src/pillars/journeys/runner.ts +1025 -0
  45. package/src/pillars/journeys/sandbox.ts +406 -0
  46. package/src/pillars/journeys/spec/loader.ts +152 -0
  47. package/src/pillars/journeys/spec/schema.ts +208 -0
  48. package/src/pillars/journeys/teardown.ts +98 -0
  49. package/src/pillars/journeys/tools/browser-session.ts +219 -0
  50. package/src/pillars/journeys/tools/browser-tool.ts +914 -0
  51. package/src/pillars/journeys/tools/http-tool.ts +564 -0
  52. package/src/pillars/journeys/tools/ledger.ts +278 -0
  53. package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
  54. package/src/pillars/journeys/verify/assertions.ts +502 -0
  55. package/src/pillars/journeys/verify/verdict.ts +71 -0
  56. package/src/pillars/perf/build-report.ts +36 -0
  57. package/src/pillars/perf/k6.config.js +112 -0
  58. package/src/pillars/perf/run.ts +345 -0
  59. package/src/pillars/perf/scenarios/load-profile.js +97 -0
  60. package/src/pillars/perf/staging.ts +73 -0
  61. package/src/pillars/security/discover.ts +108 -0
  62. package/src/pillars/security/findings.ts +151 -0
  63. package/src/pillars/security/plan.ts +355 -0
  64. package/src/pillars/security/run.ts +729 -0
  65. package/src/pillars/smoke/admin-probe.ts +68 -0
  66. package/src/pillars/smoke/base-url.ts +56 -0
  67. package/src/pillars/smoke/cleanup.ts +8 -0
  68. package/src/pillars/smoke/discover.ts +237 -0
  69. package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
  70. package/src/pillars/smoke/fixture-manager.ts +217 -0
  71. package/src/pillars/smoke/fixtures.ts +468 -0
  72. package/src/pillars/smoke/global-setup-all.ts +262 -0
  73. package/src/pillars/smoke/global-setup-global.ts +262 -0
  74. package/src/pillars/smoke/global-setup.ts +273 -0
  75. package/src/pillars/smoke/global-teardown-all.ts +8 -0
  76. package/src/pillars/smoke/global-teardown-global.ts +8 -0
  77. package/src/pillars/smoke/global-teardown.ts +139 -0
  78. package/src/pillars/smoke/playwright-all.config.ts +147 -0
  79. package/src/pillars/smoke/playwright-global.config.ts +141 -0
  80. package/src/pillars/smoke/playwright.config.ts +162 -0
  81. package/src/pillars/smoke/setup-env.ts +132 -0
  82. package/src/pillars/smoke/setup-global.ts +126 -0
  83. package/src/pillars/smoke/setup.ts +115 -0
  84. package/src/pillars/smoke/smoke-repo.ts +16 -0
  85. package/src/pillars/smoke/staging.ts +49 -0
  86. package/src/pillars/suites/run.ts +147 -0
  87. package/src/scripts/configure-aws-profiles.ts +114 -0
  88. package/src/scripts/register-repos.ts +307 -0
  89. package/src/scripts/setup-default-workspace.ts +203 -0
  90. package/src/shared/applications.ts +138 -0
  91. package/src/shared/auth/api-token.ts +416 -0
  92. package/src/shared/auth/auth0-helper.ts +284 -0
  93. package/src/shared/auth/headless-login.ts +265 -0
  94. package/src/shared/auth/jwt-helper.ts +186 -0
  95. package/src/shared/auth/sudo-vault.ts +69 -0
  96. package/src/shared/auth/test-accounts.ts +98 -0
  97. package/src/shared/auth/token-injection.ts +184 -0
  98. package/src/shared/auth/token-providers/auth0.ts +80 -0
  99. package/src/shared/auth/token-providers/index.ts +182 -0
  100. package/src/shared/auth/token-refresh.ts +273 -0
  101. package/src/shared/banner.ts +15 -0
  102. package/src/shared/brand.ts +79 -0
  103. package/src/shared/cli-tabs.ts +131 -0
  104. package/src/shared/constants.ts +128 -0
  105. package/src/shared/den-config.ts +566 -0
  106. package/src/shared/dev-metrics/emit.ts +354 -0
  107. package/src/shared/edge/config.ts +229 -0
  108. package/src/shared/edge/render.ts +221 -0
  109. package/src/shared/edge/runtime.ts +268 -0
  110. package/src/shared/edge/types.ts +62 -0
  111. package/src/shared/engine.ts +67 -0
  112. package/src/shared/environment/aws-profiles.ts +49 -0
  113. package/src/shared/environment/env-selector.ts +161 -0
  114. package/src/shared/environment/playwright-env.ts +82 -0
  115. package/src/shared/environment-manager/catalog.ts +77 -0
  116. package/src/shared/environment-manager/clone-guard.ts +163 -0
  117. package/src/shared/environment-manager/dag.ts +286 -0
  118. package/src/shared/environment-manager/default-workspace.ts +182 -0
  119. package/src/shared/environment-manager/derivation/alias.ts +90 -0
  120. package/src/shared/environment-manager/derivation/cache.ts +97 -0
  121. package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
  122. package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
  123. package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
  124. package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
  125. package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
  126. package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
  127. package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
  128. package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
  129. package/src/shared/environment-manager/derivation/scan.ts +160 -0
  130. package/src/shared/environment-manager/derivation/text.ts +40 -0
  131. package/src/shared/environment-manager/derivation/types.ts +69 -0
  132. package/src/shared/environment-manager/health.ts +589 -0
  133. package/src/shared/environment-manager/index.ts +6 -0
  134. package/src/shared/environment-manager/load-applications.ts +78 -0
  135. package/src/shared/environment-manager/log-tail.ts +212 -0
  136. package/src/shared/environment-manager/make-targets.ts +78 -0
  137. package/src/shared/environment-manager/opctl.ts +334 -0
  138. package/src/shared/environment-manager/readiness.ts +93 -0
  139. package/src/shared/environment-manager/register-local.ts +72 -0
  140. package/src/shared/environment-manager/secrets-check.ts +260 -0
  141. package/src/shared/environment-manager/service-env.ts +110 -0
  142. package/src/shared/environment-manager/service-runner.ts +1588 -0
  143. package/src/shared/environment-manager/up-plan.ts +302 -0
  144. package/src/shared/environment-manager/workspace-manager.ts +905 -0
  145. package/src/shared/environment-manager/workspaces.ts +860 -0
  146. package/src/shared/manifest/deps.ts +27 -0
  147. package/src/shared/manifest/migrate-manifest.ts +101 -0
  148. package/src/shared/manifest/read-manifest.ts +160 -0
  149. package/src/shared/manifest/schema.ts +299 -0
  150. package/src/shared/manifest/service-links.ts +93 -0
  151. package/src/shared/manifest/validate-manifest.ts +140 -0
  152. package/src/shared/manifest/workspace-manifests.ts +92 -0
  153. package/src/shared/open-url.ts +122 -0
  154. package/src/shared/org.ts +44 -0
  155. package/src/shared/paths.ts +178 -0
  156. package/src/shared/perf/optional-fields.ts +105 -0
  157. package/src/shared/perf/report-normalizer.ts +404 -0
  158. package/src/shared/perf/report-paths.ts +19 -0
  159. package/src/shared/perf/report-store.ts +110 -0
  160. package/src/shared/perf/report-types.ts +70 -0
  161. package/src/shared/perf/test-paths.ts +25 -0
  162. package/src/shared/process/log-rotation.ts +147 -0
  163. package/src/shared/process/spawn-streaming.ts +229 -0
  164. package/src/shared/product.ts +92 -0
  165. package/src/shared/profile.ts +141 -0
  166. package/src/shared/repo-kind.ts +32 -0
  167. package/src/shared/reporting/jsonl-reporter.ts +131 -0
  168. package/src/shared/reporting/slack-reporter.ts +340 -0
  169. package/src/shared/results/result-reader.ts +266 -0
  170. package/src/shared/results/result-writer.ts +168 -0
  171. package/src/shared/secrets/doppler.ts +126 -0
  172. package/src/shared/secrets/env.ts +48 -0
  173. package/src/shared/secrets/index.ts +197 -0
  174. package/src/shared/secrets/keys.ts +33 -0
  175. package/src/shared/secrets/onepassword.ts +75 -0
  176. package/src/shared/secrets/types.ts +52 -0
  177. package/src/shared/service-url.ts +119 -0
  178. package/src/shared/smoke/branch-resolver.ts +141 -0
  179. package/src/shared/smoke/git-remote.ts +70 -0
  180. package/src/shared/smoke/repo-cloner.ts +151 -0
  181. package/src/shared/stacks/config.ts +137 -0
  182. package/src/shared/stacks/manifest-commands.ts +88 -0
  183. package/src/shared/stacks/render.ts +253 -0
  184. package/src/shared/stacks/runtime.ts +334 -0
  185. package/src/shared/stacks/types.ts +53 -0
  186. package/src/shared/terminal/tabs.ts +212 -0
  187. package/src/shared/test-discovery/module-scanner.ts +569 -0
  188. package/src/shared/types/index.ts +678 -0
  189. package/src/shared/ui-marker.ts +19 -0
  190. package/src/ui/cli/applications-command.ts +116 -0
  191. package/src/ui/cli/config-command.ts +68 -0
  192. package/src/ui/cli/create-main.ts +9 -0
  193. package/src/ui/cli/create.ts +360 -0
  194. package/src/ui/cli/edge-command.ts +91 -0
  195. package/src/ui/cli/flag-parser.ts +246 -0
  196. package/src/ui/cli/graph-command.ts +524 -0
  197. package/src/ui/cli/health-display.ts +63 -0
  198. package/src/ui/cli/index.ts +3656 -0
  199. package/src/ui/cli/init.ts +855 -0
  200. package/src/ui/cli/invocation-target.ts +175 -0
  201. package/src/ui/cli/logs-command.ts +104 -0
  202. package/src/ui/cli/module-selection.ts +31 -0
  203. package/src/ui/cli/prompts.ts +767 -0
  204. package/src/ui/cli/results-viewer.ts +436 -0
  205. package/src/ui/cli/secrets-preflight.ts +91 -0
  206. package/src/ui/cli/stack-command.ts +144 -0
  207. package/src/ui/cli/stalled-setup-recovery.ts +129 -0
  208. package/src/ui/cli/test-runner.ts +1312 -0
  209. package/src/ui/cli/web-command.ts +1073 -0
  210. package/src/ui/server/changelog-parser.ts +94 -0
  211. package/src/ui/server/daemon-reaper.ts +129 -0
  212. package/src/ui/server/editor-manager.ts +301 -0
  213. package/src/ui/server/github-service.ts +223 -0
  214. package/src/ui/server/http-helpers.ts +98 -0
  215. package/src/ui/server/index.ts +3236 -0
  216. package/src/ui/server/log-history.ts +452 -0
  217. package/src/ui/server/perf-report.ts +42 -0
  218. package/src/ui/server/public/environments.html +494 -0
  219. package/src/ui/server/public/favicon.ico +0 -0
  220. package/src/ui/server/public/header.js +1371 -0
  221. package/src/ui/server/public/index.html +2595 -0
  222. package/src/ui/server/public/local-dev.html +7405 -0
  223. package/src/ui/server/public/logs.html +1489 -0
  224. package/src/ui/server/public/perf-report.js +359 -0
  225. package/src/ui/server/public/release.html +2111 -0
  226. package/src/ui/server/public/services.html +1098 -0
  227. package/src/ui/server/public/workspace-picker.js +69 -0
  228. package/src/ui/server/release-deploy-session.ts +246 -0
  229. package/src/ui/server/release-service.ts +1125 -0
  230. package/src/ui/server/sudo-store.ts +111 -0
  231. package/src/ui/server/workspace-routes.ts +2154 -0
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Token Injection Helper
3
+ * Inject JWT tokens directly into browser storage to bypass Auth0 OAuth flow
4
+ * This allows fast, headless testing without waiting for redirects
5
+ */
6
+
7
+ import type { Page, BrowserContext } from "@playwright/test";
8
+ import type { TokenSet } from "../types/index.js";
9
+ import type { Auth0Helper } from "./auth0-helper.js";
10
+
11
+ /**
12
+ * Inject Auth0 tokens into browser localStorage
13
+ * This simulates a logged-in session without going through the OAuth flow
14
+ *
15
+ * @param page - Playwright page object
16
+ * @param tokens - JWT tokens from Auth0 password grant
17
+ * @param baseUrl - Base URL of the app (needed for localStorage scope)
18
+ */
19
+ export async function injectAuth0Tokens(
20
+ page: Page,
21
+ tokens: TokenSet,
22
+ baseUrl: string,
23
+ ): Promise<void> {
24
+ // First, navigate to the base URL so localStorage is scoped correctly
25
+ await page.goto(baseUrl);
26
+
27
+ // Inject tokens into localStorage in the format Auth0 SPA SDK expects
28
+ await page.evaluate(
29
+ ({ accessToken, idToken, refreshToken, expiresIn }) => {
30
+ // Auth0 SPA SDK storage format
31
+ const auth0Key = `@@auth0spajs@@::${window.location.origin}`;
32
+
33
+ const expiresAt = Date.now() + (expiresIn || 3600) * 1000;
34
+
35
+ const auth0Data = {
36
+ body: {
37
+ access_token: accessToken,
38
+ id_token: idToken,
39
+ refresh_token: refreshToken,
40
+ expires_in: expiresIn || 3600,
41
+ token_type: "Bearer",
42
+ scope: "openid profile email",
43
+ },
44
+ expiresAt,
45
+ };
46
+
47
+ // Store in localStorage
48
+ localStorage.setItem(auth0Key, JSON.stringify(auth0Data));
49
+
50
+ // Also store id_token separately (some apps check this)
51
+ localStorage.setItem("id_token", idToken || "");
52
+ localStorage.setItem("access_token", accessToken);
53
+
54
+ console.log("✓ Auth0 tokens injected into localStorage");
55
+ },
56
+ {
57
+ accessToken: tokens.access_token,
58
+ idToken: tokens.id_token,
59
+ refreshToken: tokens.refresh_token,
60
+ expiresIn: tokens.expires_in,
61
+ },
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Inject Auth0 session via API callback (for Next.js with @auth0/nextjs-auth0)
67
+ * This approach calls the Next.js auth callback endpoint to establish a session
68
+ *
69
+ * @param context - Playwright browser context
70
+ * @param tokens - JWT tokens from Auth0 password grant
71
+ * @param baseUrl - Base URL of the app
72
+ */
73
+ export async function injectAuth0Session(
74
+ context: BrowserContext,
75
+ tokens: TokenSet,
76
+ baseUrl: string,
77
+ ): Promise<void> {
78
+ // Create a new page for API call
79
+ const page = await context.newPage();
80
+
81
+ try {
82
+ // Call the Next.js auth callback endpoint with the code
83
+ // This establishes the session and sets HTTP-only cookies
84
+ const response = await page.request.post(`${baseUrl}/api/auth/callback`, {
85
+ data: {
86
+ code: tokens.access_token, // Some implementations use the access token
87
+ state: "test-state",
88
+ },
89
+ failOnStatusCode: false,
90
+ });
91
+
92
+ if (response.ok()) {
93
+ console.log("✓ Auth0 session established via API callback");
94
+ } else {
95
+ console.warn(
96
+ `⚠️ Auth callback returned status ${response.status()}, falling back to localStorage`,
97
+ );
98
+ // Fallback to localStorage injection
99
+ await injectAuth0Tokens(page, tokens, baseUrl);
100
+ }
101
+ } catch (error) {
102
+ console.warn(
103
+ `⚠️ Auth callback failed: ${(error as Error).message}, falling back to localStorage`,
104
+ );
105
+ // Fallback to localStorage injection
106
+ await injectAuth0Tokens(page, tokens, baseUrl);
107
+ } finally {
108
+ await page.close();
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Setup authenticated browser context with JWT tokens
114
+ * This is the main entry point for tests - gets tokens and injects them
115
+ *
116
+ * @param page - Playwright page object
117
+ * @param username - User email
118
+ * @param password - User password
119
+ * @param auth0Helper - Auth0 helper instance
120
+ * @param baseUrl - Base URL of the app
121
+ */
122
+ export async function setupAuthenticatedPage(
123
+ page: Page,
124
+ username: string,
125
+ password: string,
126
+ auth0Helper: Auth0Helper,
127
+ baseUrl: string,
128
+ ): Promise<TokenSet> {
129
+ // Get JWT tokens programmatically (no browser flow)
130
+ console.log(`🔐 Getting JWT tokens for ${username}...`);
131
+ const tokens = await auth0Helper.loginWithPassword(username, password);
132
+ console.log("✓ JWT tokens received");
133
+
134
+ // Inject tokens into browser
135
+ await injectAuth0Tokens(page, tokens, baseUrl);
136
+
137
+ return tokens;
138
+ }
139
+
140
+ /**
141
+ * Check if page has valid auth tokens in storage
142
+ */
143
+ export async function hasValidAuthTokens(page: Page): Promise<boolean> {
144
+ return await page.evaluate(() => {
145
+ // Check for Auth0 SPA SDK storage
146
+ const auth0Keys = Object.keys(localStorage).filter((key) =>
147
+ key.includes("@@auth0spajs@@"),
148
+ );
149
+
150
+ if (auth0Keys.length === 0) {
151
+ return false;
152
+ }
153
+
154
+ // Check if tokens are expired
155
+ try {
156
+ const auth0Data = JSON.parse(localStorage.getItem(auth0Keys[0]) || "{}");
157
+ const expiresAt = auth0Data.expiresAt || 0;
158
+ const now = Date.now();
159
+
160
+ return now < expiresAt;
161
+ } catch {
162
+ return false;
163
+ }
164
+ });
165
+ }
166
+
167
+ /**
168
+ * Clear all auth tokens from browser storage
169
+ */
170
+ export async function clearAuthTokens(page: Page): Promise<void> {
171
+ await page.evaluate(() => {
172
+ // Clear Auth0 SPA SDK storage
173
+ const auth0Keys = Object.keys(localStorage).filter(
174
+ (key) => key.includes("auth0") || key.includes("@@auth0spajs@@"),
175
+ );
176
+ auth0Keys.forEach((key) => localStorage.removeItem(key));
177
+
178
+ // Clear other auth-related items
179
+ localStorage.removeItem("id_token");
180
+ localStorage.removeItem("access_token");
181
+
182
+ console.log("✓ Auth tokens cleared from localStorage");
183
+ });
184
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Auth0 Resource Owner Password grant — the `auth0` token provider's mint.
3
+ *
4
+ * Reads the Auth0 domain, clientId and audience from the environment's
5
+ * `auth0` block in config/environments/<env>.json. No caching here — callers
6
+ * decide whether to persist the token.
7
+ */
8
+
9
+ import { envSelector } from "../../environment/env-selector.js";
10
+ import type { Environment } from "../../types/index.js";
11
+
12
+ interface RopcResponse {
13
+ access_token: string;
14
+ token_type: string;
15
+ expires_in: number;
16
+ error?: string;
17
+ error_description?: string;
18
+ }
19
+
20
+ /** Mint a fresh Auth0 access token via Resource Owner Password Credentials. */
21
+ export async function mintAuth0JWT(
22
+ env: Environment,
23
+ username: string,
24
+ password: string,
25
+ options: { clientSecret?: string } = {},
26
+ ): Promise<{ accessToken: string; expiresIn: number; tokenType: string }> {
27
+ const envConfig = await envSelector.loadEnvironment(env);
28
+ if (!envConfig.auth0) {
29
+ throw new Error(
30
+ `auth0 block is not set in config/environments/${env}.json`,
31
+ );
32
+ }
33
+ const { domain, clientId, audience } = envConfig.auth0;
34
+
35
+ if (!audience) {
36
+ throw new Error(
37
+ `auth0.audience is not set in config/environments/${env}.json`,
38
+ );
39
+ }
40
+
41
+ const body: Record<string, string> = {
42
+ grant_type: "password",
43
+ username,
44
+ password,
45
+ audience,
46
+ scope: "openid profile email",
47
+ client_id: clientId,
48
+ realm: process.env.E2E_AUTH0_REALM ?? "Username-Password-Authentication",
49
+ };
50
+
51
+ // client_secret is optional for public SPA clients; required for
52
+ // confidential ones. Callers may pass it explicitly (memory-only paths
53
+ // that must not mutate process.env); the env var remains the default.
54
+ const clientSecret =
55
+ options.clientSecret ?? process.env.E2E_AUTH0_CLIENT_SECRET;
56
+ if (clientSecret) body.client_secret = clientSecret;
57
+
58
+ const response = await fetch(`https://${domain}/oauth/token`, {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: JSON.stringify(body),
62
+ signal: AbortSignal.timeout(15000),
63
+ });
64
+
65
+ const data = (await response.json()) as RopcResponse;
66
+
67
+ if (!response.ok || data.error) {
68
+ const desc =
69
+ data.error_description ?? data.error ?? `HTTP ${response.status}`;
70
+ throw new Error(
71
+ `Auth0 ROPC failed for "${username}" (${env}): ${data.error ?? response.status} — ${desc}`,
72
+ );
73
+ }
74
+
75
+ return {
76
+ accessToken: data.access_token,
77
+ expiresIn: data.expires_in,
78
+ tokenType: data.token_type,
79
+ };
80
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Token-provider strategy — how Den mints a bearer token for a test user.
3
+ *
4
+ * Selected per environment by `auth.provider` in config/environments/<env>.json:
5
+ *
6
+ * auth0 Resource Owner Password grant against an Auth0 tenant
7
+ * cognito AWS Cognito InitiateAuth (USER_PASSWORD_AUTH) over HTTPS — no SDK
8
+ * local-jwt self-signed HS256 token for local stacks that trust a shared secret
9
+ * none environment has no auth; minting throws
10
+ *
11
+ * When `auth` is omitted, an `auth0` block implies "auth0", else "none".
12
+ * Adding a provider = implement TokenProvider and register it in PROVIDERS.
13
+ */
14
+ import jwt from "jsonwebtoken";
15
+ import { envSelector } from "../../environment/env-selector.js";
16
+ import { getSecretsProvider } from "../../secrets/index.js";
17
+ import type {
18
+ AuthProviderName,
19
+ EnvironmentConfig,
20
+ Environment,
21
+ } from "../../types/index.js";
22
+ import { mintAuth0JWT } from "./auth0.js";
23
+
24
+ export interface MintedToken {
25
+ accessToken: string;
26
+ expiresIn: number;
27
+ tokenType: string;
28
+ }
29
+
30
+ export interface MintOptions {
31
+ clientSecret?: string;
32
+ fetchImpl?: typeof fetch;
33
+ env?: NodeJS.ProcessEnv;
34
+ }
35
+
36
+ export interface TokenProvider {
37
+ readonly name: AuthProviderName;
38
+ mint(
39
+ envConfig: EnvironmentConfig,
40
+ username: string,
41
+ password: string,
42
+ options?: MintOptions,
43
+ ): Promise<MintedToken>;
44
+ }
45
+
46
+ export function resolveAuthProviderName(
47
+ cfg: EnvironmentConfig,
48
+ ): AuthProviderName {
49
+ return cfg.auth?.provider ?? (cfg.auth0 ? "auth0" : "none");
50
+ }
51
+
52
+ const auth0Provider: TokenProvider = {
53
+ name: "auth0",
54
+ mint: (cfg, u, p, o = {}) =>
55
+ mintAuth0JWT(cfg.name as Environment, u, p, {
56
+ clientSecret: o.clientSecret,
57
+ }),
58
+ };
59
+
60
+ interface CognitoAuthResult {
61
+ AuthenticationResult?: {
62
+ AccessToken?: string;
63
+ IdToken?: string;
64
+ ExpiresIn?: number;
65
+ TokenType?: string;
66
+ };
67
+ ChallengeName?: string;
68
+ __type?: string;
69
+ message?: string;
70
+ }
71
+
72
+ const cognitoProvider: TokenProvider = {
73
+ name: "cognito",
74
+ async mint(cfg, username, password, o = {}) {
75
+ const c = cfg.auth?.cognito;
76
+ if (!c) throw new Error(`auth.cognito is not set for ${cfg.name}`);
77
+ const res = await (o.fetchImpl ?? fetch)(
78
+ `https://cognito-idp.${c.region}.amazonaws.com/`,
79
+ {
80
+ method: "POST",
81
+ headers: {
82
+ "Content-Type": "application/x-amz-json-1.1",
83
+ "X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth",
84
+ },
85
+ body: JSON.stringify({
86
+ AuthFlow: "USER_PASSWORD_AUTH",
87
+ ClientId: c.clientId,
88
+ AuthParameters: { USERNAME: username, PASSWORD: password },
89
+ }),
90
+ signal: AbortSignal.timeout(15_000),
91
+ },
92
+ );
93
+ const data = (await res.json()) as CognitoAuthResult;
94
+ const r = data.AuthenticationResult;
95
+ const token = c.useIdToken ? r?.IdToken : r?.AccessToken;
96
+ if (!res.ok || !token) {
97
+ const why = data.ChallengeName
98
+ ? `challenge ${data.ChallengeName} (complete it once in the hosted UI)`
99
+ : `${data.__type ?? res.status} — ${data.message ?? "no token returned"}`;
100
+ throw new Error(
101
+ `Cognito login failed for "${username}" (${cfg.name}): ${why}`,
102
+ );
103
+ }
104
+ return {
105
+ accessToken: token,
106
+ expiresIn: r?.ExpiresIn ?? 3600,
107
+ tokenType: r?.TokenType ?? "Bearer",
108
+ };
109
+ },
110
+ };
111
+
112
+ const localJwtProvider: TokenProvider = {
113
+ name: "local-jwt",
114
+ async mint(cfg, username, _password, o = {}) {
115
+ const l = cfg.auth?.localJwt;
116
+ if (!l) throw new Error(`auth.localJwt is not set for ${cfg.name}`);
117
+ const env = o.env ?? process.env;
118
+ const secret =
119
+ env[l.secretKey] ??
120
+ (await getSecretsProvider().tryGet({
121
+ vault: "",
122
+ item: "",
123
+ field: l.secretKey,
124
+ key: l.secretKey,
125
+ }));
126
+ if (!secret) {
127
+ throw new Error(
128
+ `local-jwt signing secret ${l.secretKey} not found in env or the secrets backend`,
129
+ );
130
+ }
131
+ const ttl = l.ttlSeconds ?? 3600;
132
+ const claims = { email: username, ...l.claims };
133
+ const accessToken = jwt.sign(claims, secret, {
134
+ algorithm: "HS256",
135
+ // A configured `sub` claim (e.g. a user id) wins over the username.
136
+ ...("sub" in claims ? {} : { subject: username }),
137
+ expiresIn: ttl,
138
+ ...(l.issuer ? { issuer: l.issuer } : {}),
139
+ ...(l.audience ? { audience: l.audience } : {}),
140
+ });
141
+ return { accessToken, expiresIn: ttl, tokenType: "Bearer" };
142
+ },
143
+ };
144
+
145
+ const noneProvider: TokenProvider = {
146
+ name: "none",
147
+ async mint(cfg) {
148
+ throw new Error(
149
+ `Environment ${cfg.name} has no auth provider configured (auth.provider: none). ` +
150
+ `Set auth.provider in config/environments/${cfg.name}.json.`,
151
+ );
152
+ },
153
+ };
154
+
155
+ const PROVIDERS: Record<AuthProviderName, TokenProvider> = {
156
+ auth0: auth0Provider,
157
+ cognito: cognitoProvider,
158
+ "local-jwt": localJwtProvider,
159
+ none: noneProvider,
160
+ };
161
+
162
+ export function getTokenProvider(name: AuthProviderName): TokenProvider {
163
+ const p = PROVIDERS[name];
164
+ if (!p) throw new Error(`Unknown auth provider "${String(name)}"`);
165
+ return p;
166
+ }
167
+
168
+ /** Mint a token for `env` with whatever provider that environment configures. */
169
+ export async function mintAccessToken(
170
+ env: Environment,
171
+ username: string,
172
+ password: string,
173
+ options: MintOptions = {},
174
+ ): Promise<MintedToken> {
175
+ const cfg = await envSelector.loadEnvironment(env);
176
+ return getTokenProvider(resolveAuthProviderName(cfg)).mint(
177
+ cfg,
178
+ username,
179
+ password,
180
+ options,
181
+ );
182
+ }