@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.
- package/CHANGELOG.md +67 -0
- package/LICENSE +21 -0
- package/README.md +165 -1
- package/bin/den +61 -0
- package/docs/distributions.md +256 -0
- package/make/demo.mk +82 -0
- package/make/den.mk +80 -0
- package/make/journeys.mk +57 -0
- package/make/local_dev.mk +154 -0
- package/make/security.mk +59 -0
- package/make/test.mk +405 -0
- package/package.json +99 -4
- package/scripts/auth-configure.ts +415 -0
- package/scripts/heal-config.ts +92 -0
- package/scripts/jsonl-playwright-reporter.js +70 -0
- package/scripts/manifest-read.cjs +91 -0
- package/scripts/setup-1password.ts +570 -0
- package/scripts/setup-secrets.ts +149 -0
- package/scripts/view-results.js +156 -0
- package/service/install.sh +112 -0
- package/src/pillars/api/fixtures/index.ts +128 -0
- package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
- package/src/pillars/api/global-setup.ts +103 -0
- package/src/pillars/api/playwright.config.ts +105 -0
- package/src/pillars/api/schemas/example-api.json +110 -0
- package/src/pillars/api/tests/health/health.spec.ts +19 -0
- package/src/pillars/api/tests/health/manifest.yml +6 -0
- package/src/pillars/browser/fixtures/index.ts +232 -0
- package/src/pillars/browser/fixtures/package.json +10 -0
- package/src/pillars/browser/global-setup.ts +140 -0
- package/src/pillars/browser/global-teardown.ts +48 -0
- package/src/pillars/browser/playwright.config.ts +212 -0
- package/src/pillars/browser/setup.ts +219 -0
- package/src/pillars/journeys/app-config.ts +237 -0
- package/src/pillars/journeys/discover.ts +160 -0
- package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
- package/src/pillars/journeys/observability/events.ts +288 -0
- package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
- package/src/pillars/journeys/report/friction.ts +304 -0
- package/src/pillars/journeys/report/junit.ts +105 -0
- package/src/pillars/journeys/report/render.ts +273 -0
- package/src/pillars/journeys/report/results.ts +323 -0
- package/src/pillars/journeys/run.ts +673 -0
- package/src/pillars/journeys/runner.ts +1025 -0
- package/src/pillars/journeys/sandbox.ts +406 -0
- package/src/pillars/journeys/spec/loader.ts +152 -0
- package/src/pillars/journeys/spec/schema.ts +208 -0
- package/src/pillars/journeys/teardown.ts +98 -0
- package/src/pillars/journeys/tools/browser-session.ts +219 -0
- package/src/pillars/journeys/tools/browser-tool.ts +914 -0
- package/src/pillars/journeys/tools/http-tool.ts +564 -0
- package/src/pillars/journeys/tools/ledger.ts +278 -0
- package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
- package/src/pillars/journeys/verify/assertions.ts +502 -0
- package/src/pillars/journeys/verify/verdict.ts +71 -0
- package/src/pillars/perf/build-report.ts +36 -0
- package/src/pillars/perf/k6.config.js +112 -0
- package/src/pillars/perf/run.ts +345 -0
- package/src/pillars/perf/scenarios/load-profile.js +97 -0
- package/src/pillars/perf/staging.ts +73 -0
- package/src/pillars/security/discover.ts +108 -0
- package/src/pillars/security/findings.ts +151 -0
- package/src/pillars/security/plan.ts +355 -0
- package/src/pillars/security/run.ts +729 -0
- package/src/pillars/smoke/admin-probe.ts +68 -0
- package/src/pillars/smoke/base-url.ts +56 -0
- package/src/pillars/smoke/cleanup.ts +8 -0
- package/src/pillars/smoke/discover.ts +237 -0
- package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
- package/src/pillars/smoke/fixture-manager.ts +217 -0
- package/src/pillars/smoke/fixtures.ts +468 -0
- package/src/pillars/smoke/global-setup-all.ts +262 -0
- package/src/pillars/smoke/global-setup-global.ts +262 -0
- package/src/pillars/smoke/global-setup.ts +273 -0
- package/src/pillars/smoke/global-teardown-all.ts +8 -0
- package/src/pillars/smoke/global-teardown-global.ts +8 -0
- package/src/pillars/smoke/global-teardown.ts +139 -0
- package/src/pillars/smoke/playwright-all.config.ts +147 -0
- package/src/pillars/smoke/playwright-global.config.ts +141 -0
- package/src/pillars/smoke/playwright.config.ts +162 -0
- package/src/pillars/smoke/setup-env.ts +132 -0
- package/src/pillars/smoke/setup-global.ts +126 -0
- package/src/pillars/smoke/setup.ts +115 -0
- package/src/pillars/smoke/smoke-repo.ts +16 -0
- package/src/pillars/smoke/staging.ts +49 -0
- package/src/pillars/suites/run.ts +147 -0
- package/src/scripts/configure-aws-profiles.ts +114 -0
- package/src/scripts/register-repos.ts +307 -0
- package/src/scripts/setup-default-workspace.ts +203 -0
- package/src/shared/applications.ts +138 -0
- package/src/shared/auth/api-token.ts +416 -0
- package/src/shared/auth/auth0-helper.ts +284 -0
- package/src/shared/auth/headless-login.ts +265 -0
- package/src/shared/auth/jwt-helper.ts +186 -0
- package/src/shared/auth/sudo-vault.ts +69 -0
- package/src/shared/auth/test-accounts.ts +98 -0
- package/src/shared/auth/token-injection.ts +184 -0
- package/src/shared/auth/token-providers/auth0.ts +80 -0
- package/src/shared/auth/token-providers/index.ts +182 -0
- package/src/shared/auth/token-refresh.ts +273 -0
- package/src/shared/banner.ts +15 -0
- package/src/shared/brand.ts +79 -0
- package/src/shared/cli-tabs.ts +131 -0
- package/src/shared/constants.ts +128 -0
- package/src/shared/den-config.ts +566 -0
- package/src/shared/dev-metrics/emit.ts +354 -0
- package/src/shared/edge/config.ts +229 -0
- package/src/shared/edge/render.ts +221 -0
- package/src/shared/edge/runtime.ts +268 -0
- package/src/shared/edge/types.ts +62 -0
- package/src/shared/engine.ts +67 -0
- package/src/shared/environment/aws-profiles.ts +49 -0
- package/src/shared/environment/env-selector.ts +161 -0
- package/src/shared/environment/playwright-env.ts +82 -0
- package/src/shared/environment-manager/catalog.ts +77 -0
- package/src/shared/environment-manager/clone-guard.ts +163 -0
- package/src/shared/environment-manager/dag.ts +286 -0
- package/src/shared/environment-manager/default-workspace.ts +182 -0
- package/src/shared/environment-manager/derivation/alias.ts +90 -0
- package/src/shared/environment-manager/derivation/cache.ts +97 -0
- package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
- package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
- package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
- package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
- package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
- package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
- package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
- package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
- package/src/shared/environment-manager/derivation/scan.ts +160 -0
- package/src/shared/environment-manager/derivation/text.ts +40 -0
- package/src/shared/environment-manager/derivation/types.ts +69 -0
- package/src/shared/environment-manager/health.ts +589 -0
- package/src/shared/environment-manager/index.ts +6 -0
- package/src/shared/environment-manager/load-applications.ts +78 -0
- package/src/shared/environment-manager/log-tail.ts +212 -0
- package/src/shared/environment-manager/make-targets.ts +78 -0
- package/src/shared/environment-manager/opctl.ts +334 -0
- package/src/shared/environment-manager/readiness.ts +93 -0
- package/src/shared/environment-manager/register-local.ts +72 -0
- package/src/shared/environment-manager/secrets-check.ts +260 -0
- package/src/shared/environment-manager/service-env.ts +110 -0
- package/src/shared/environment-manager/service-runner.ts +1588 -0
- package/src/shared/environment-manager/up-plan.ts +302 -0
- package/src/shared/environment-manager/workspace-manager.ts +905 -0
- package/src/shared/environment-manager/workspaces.ts +860 -0
- package/src/shared/manifest/deps.ts +27 -0
- package/src/shared/manifest/migrate-manifest.ts +101 -0
- package/src/shared/manifest/read-manifest.ts +160 -0
- package/src/shared/manifest/schema.ts +299 -0
- package/src/shared/manifest/service-links.ts +93 -0
- package/src/shared/manifest/validate-manifest.ts +140 -0
- package/src/shared/manifest/workspace-manifests.ts +92 -0
- package/src/shared/open-url.ts +122 -0
- package/src/shared/org.ts +44 -0
- package/src/shared/paths.ts +178 -0
- package/src/shared/perf/optional-fields.ts +105 -0
- package/src/shared/perf/report-normalizer.ts +404 -0
- package/src/shared/perf/report-paths.ts +19 -0
- package/src/shared/perf/report-store.ts +110 -0
- package/src/shared/perf/report-types.ts +70 -0
- package/src/shared/perf/test-paths.ts +25 -0
- package/src/shared/process/log-rotation.ts +147 -0
- package/src/shared/process/spawn-streaming.ts +229 -0
- package/src/shared/product.ts +92 -0
- package/src/shared/profile.ts +141 -0
- package/src/shared/repo-kind.ts +32 -0
- package/src/shared/reporting/jsonl-reporter.ts +131 -0
- package/src/shared/reporting/slack-reporter.ts +340 -0
- package/src/shared/results/result-reader.ts +266 -0
- package/src/shared/results/result-writer.ts +168 -0
- package/src/shared/secrets/doppler.ts +126 -0
- package/src/shared/secrets/env.ts +48 -0
- package/src/shared/secrets/index.ts +197 -0
- package/src/shared/secrets/keys.ts +33 -0
- package/src/shared/secrets/onepassword.ts +75 -0
- package/src/shared/secrets/types.ts +52 -0
- package/src/shared/service-url.ts +119 -0
- package/src/shared/smoke/branch-resolver.ts +141 -0
- package/src/shared/smoke/git-remote.ts +70 -0
- package/src/shared/smoke/repo-cloner.ts +151 -0
- package/src/shared/stacks/config.ts +137 -0
- package/src/shared/stacks/manifest-commands.ts +88 -0
- package/src/shared/stacks/render.ts +253 -0
- package/src/shared/stacks/runtime.ts +334 -0
- package/src/shared/stacks/types.ts +53 -0
- package/src/shared/terminal/tabs.ts +212 -0
- package/src/shared/test-discovery/module-scanner.ts +569 -0
- package/src/shared/types/index.ts +678 -0
- package/src/shared/ui-marker.ts +19 -0
- package/src/ui/cli/applications-command.ts +116 -0
- package/src/ui/cli/config-command.ts +68 -0
- package/src/ui/cli/create-main.ts +9 -0
- package/src/ui/cli/create.ts +360 -0
- package/src/ui/cli/edge-command.ts +91 -0
- package/src/ui/cli/flag-parser.ts +246 -0
- package/src/ui/cli/graph-command.ts +524 -0
- package/src/ui/cli/health-display.ts +63 -0
- package/src/ui/cli/index.ts +3656 -0
- package/src/ui/cli/init.ts +855 -0
- package/src/ui/cli/invocation-target.ts +175 -0
- package/src/ui/cli/logs-command.ts +104 -0
- package/src/ui/cli/module-selection.ts +31 -0
- package/src/ui/cli/prompts.ts +767 -0
- package/src/ui/cli/results-viewer.ts +436 -0
- package/src/ui/cli/secrets-preflight.ts +91 -0
- package/src/ui/cli/stack-command.ts +144 -0
- package/src/ui/cli/stalled-setup-recovery.ts +129 -0
- package/src/ui/cli/test-runner.ts +1312 -0
- package/src/ui/cli/web-command.ts +1073 -0
- package/src/ui/server/changelog-parser.ts +94 -0
- package/src/ui/server/daemon-reaper.ts +129 -0
- package/src/ui/server/editor-manager.ts +301 -0
- package/src/ui/server/github-service.ts +223 -0
- package/src/ui/server/http-helpers.ts +98 -0
- package/src/ui/server/index.ts +3236 -0
- package/src/ui/server/log-history.ts +452 -0
- package/src/ui/server/perf-report.ts +42 -0
- package/src/ui/server/public/environments.html +494 -0
- package/src/ui/server/public/favicon.ico +0 -0
- package/src/ui/server/public/header.js +1371 -0
- package/src/ui/server/public/index.html +2595 -0
- package/src/ui/server/public/local-dev.html +7405 -0
- package/src/ui/server/public/logs.html +1489 -0
- package/src/ui/server/public/perf-report.js +359 -0
- package/src/ui/server/public/release.html +2111 -0
- package/src/ui/server/public/services.html +1098 -0
- package/src/ui/server/public/workspace-picker.js +69 -0
- package/src/ui/server/release-deploy-session.ts +246 -0
- package/src/ui/server/release-service.ts +1125 -0
- package/src/ui/server/sudo-store.ts +111 -0
- 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
|
+
}
|