-
Notifications
You must be signed in to change notification settings - Fork 513
Wildcard domains #830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Wildcard domains #830
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
dd48f4f
Wildcard domains
N2D4 0ae980b
fix
N2D4 bab1c28
Merge branch 'dev' into wildcard-domains
N2D4 753e870
Merge dev into wildcard-domains
N2D4 c340f25
Merge dev into wildcard-domains
N2D4 f41243a
Merge dev into wildcard-domains
N2D4 b697f44
Merge dev into wildcard-domains
N2D4 8524a8a
Update apps/backend/src/app/api/latest/auth/oauth/callback/[provider_…
N2D4 31def15
Merge branch 'dev' into wildcard-domains
N2D4 05f5e44
move CLAUDE-KNOWLEDGE
N2D4 ee9be7d
refactor: use Map for defaultPorts to avoid prototype pollution
claude[bot] b9a0b09
Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domai…
N2D4 17b7e2b
Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domai…
N2D4 3ed23da
Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domai…
N2D4 a56ae73
Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-do…
N2D4 d22367f
Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-do…
N2D4 0e4bb55
Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-do…
N2D4 120dfc1
Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-do…
N2D4 da6d04e
Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-do…
N2D4 3eb48f1
fix
N2D4 414536c
Fix test issues: replace JSON utils, remove unused imports, fix config
claude[bot] cd46fb2
Merge dev into wildcard-domains
N2D4 b55e385
Merge dev into wildcard-domains
N2D4 e0e8a3a
Merge branch 'dev' into wildcard-domains
N2D4 29e9c2b
Fix path concatenation vulnerability in user page
claude[bot] 713591c
Fix wildcard domains test failures by adding wildcard URL validation …
claude[bot] 89930e8
Merge branch 'dev' into wildcard-domains
N2D4 3961fa6
Fix ESM import and improve wildcard URL validation
claude[bot] fa81c3c
fixes
N2D4 2d93d25
fix tests (?)
N2D4 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| # CLAUDE-KNOWLEDGE.md | ||
|
|
||
| This file documents key learnings from implementing wildcard domain support in Stack Auth, organized in Q&A format. | ||
|
|
||
| ## OAuth Flow and Validation | ||
|
|
||
| ### Q: Where does OAuth redirect URL validation happen in the flow? | ||
| A: The validation happens in the callback endpoint (`/api/v1/auth/oauth/callback/[provider_id]/route.tsx`), not in the authorize endpoint. The authorize endpoint just stores the redirect URL and redirects to the OAuth provider. The actual validation occurs when the OAuth provider calls back, and the oauth2-server library validates the redirect URL. | ||
|
|
||
| ### Q: How do you test OAuth flows that should fail? | ||
| A: Use `Auth.OAuth.getMaybeFailingAuthorizationCode()` instead of `Auth.OAuth.getAuthorizationCode()`. The latter expects success (status 303), while the former allows you to test failure cases. The failure happens at the callback stage with a 400 status and specific error message. | ||
|
|
||
| ### Q: What error is thrown for invalid redirect URLs in OAuth? | ||
| A: The callback endpoint returns a 400 status with the message: "Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard." | ||
|
|
||
| ## Wildcard Pattern Implementation | ||
|
|
||
| ### Q: How do you handle ** vs * precedence in regex patterns? | ||
| A: Use a placeholder approach to prevent ** from being corrupted when replacing *: | ||
| ```typescript | ||
| const doubleWildcardPlaceholder = '\x00DOUBLE_WILDCARD\x00'; | ||
| regexPattern = regexPattern.replace(/\*\*/g, doubleWildcardPlaceholder); | ||
| regexPattern = regexPattern.replace(/\*/g, '[^.]*'); | ||
| regexPattern = regexPattern.replace(new RegExp(doubleWildcardPlaceholder, 'g'), '.*'); | ||
| ``` | ||
|
|
||
| ### Q: Why can't you use `new URL()` with wildcard domains? | ||
| A: Wildcard characters (* and **) are not valid in URLs and will cause parsing errors. For wildcard domains, you need to manually parse the URL components instead of using the URL constructor. | ||
|
|
||
| ### Q: How do you validate URLs with wildcards? | ||
| A: Extract the hostname pattern manually and use `matchHostnamePattern()`: | ||
| ```typescript | ||
| const protocolEnd = domain.baseUrl.indexOf('://'); | ||
| const protocol = domain.baseUrl.substring(0, protocolEnd + 3); | ||
| const afterProtocol = domain.baseUrl.substring(protocolEnd + 3); | ||
| const pathStart = afterProtocol.indexOf('/'); | ||
| const hostnamePattern = pathStart === -1 ? afterProtocol : afterProtocol.substring(0, pathStart); | ||
| ``` | ||
|
Comment on lines
+30
to
+38
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Make the manual hostname extraction robust (no scheme or path edge cases) The snippet assumes a scheme is present; protocolEnd can be -1, leading to surprising substrings. Also handle empty paths and trim whitespace. -const protocolEnd = domain.baseUrl.indexOf('://');
-const protocol = domain.baseUrl.substring(0, protocolEnd + 3);
-const afterProtocol = domain.baseUrl.substring(protocolEnd + 3);
-const pathStart = afterProtocol.indexOf('/');
-const hostnamePattern = pathStart === -1 ? afterProtocol : afterProtocol.substring(0, pathStart);
+const raw = domain.baseUrl.trim();
+const schemeSep = raw.indexOf('://');
+const afterProtocol = schemeSep === -1 ? raw : raw.slice(schemeSep + 3);
+const slashIdx = afterProtocol.indexOf('/');
+const authority = slashIdx === -1 ? afterProtocol : afterProtocol.slice(0, slashIdx);
+// Strip optional port for hostname-only matching
+const portIdx = authority.lastIndexOf(':');
+const hostnamePattern = portIdx > -1 ? authority.slice(0, portIdx) : authority; |
||
|
|
||
| ## Testing Best Practices | ||
|
|
||
| ### Q: How should you run multiple independent test commands? | ||
| A: Use parallel execution by batching tool calls together: | ||
| ```typescript | ||
| // Good - runs in parallel | ||
| const [result1, result2] = await Promise.all([ | ||
| niceBackendFetch("/endpoint1"), | ||
| niceBackendFetch("/endpoint2") | ||
| ]); | ||
|
|
||
| // In E2E tests, the framework handles this automatically when you | ||
| // batch multiple tool calls in a single response | ||
| ``` | ||
|
|
||
| ### Q: What's the correct way to update project configuration in E2E tests? | ||
| A: Use the `/api/v1/internal/config/override` endpoint with PATCH method and admin access token: | ||
| ```typescript | ||
| await niceBackendFetch("/api/v1/internal/config/override", { | ||
| method: "PATCH", | ||
| accessType: "admin", | ||
| headers: { | ||
| 'x-stack-admin-access-token': adminAccessToken, | ||
| }, | ||
| body: { | ||
| config_override_string: JSON.stringify({ | ||
| 'domains.trustedDomains.name': { baseUrl: '...', handlerPath: '...' } | ||
| }), | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| ## Code Organization | ||
|
|
||
| ### Q: Where does domain validation logic belong? | ||
| A: Core validation functions (`isValidHostnameWithWildcards`, `matchHostnamePattern`) belong in the shared utils package (`packages/stack-shared/src/utils/urls.tsx`) so they can be used by both frontend and backend. | ||
|
|
||
| ### Q: How do you simplify validation logic with wildcards? | ||
| A: Replace wildcards with valid placeholders before validation: | ||
| ```typescript | ||
| const normalizedDomain = domain.replace(/\*+/g, 'wildcard-placeholder'); | ||
| url = new URL(normalizedDomain); // Now this won't throw | ||
| ``` | ||
|
|
||
| ## Debugging E2E Tests | ||
|
|
||
| ### Q: What does "ECONNREFUSED" mean in E2E tests? | ||
| A: The backend server isn't running. Make sure to start the backend with `pnpm dev` before running E2E tests. | ||
|
|
||
| ### Q: How do you debug which stage of OAuth flow is failing? | ||
| A: Check the error location: | ||
| - Authorize endpoint (307 redirect) - Initial request succeeded | ||
| - Callback endpoint (400 error) - Validation failed during callback | ||
| - Token endpoint (400 error) - Validation failed during token exchange | ||
|
|
||
| ## Git and Development Workflow | ||
|
|
||
| ### Q: How should you format git commit messages in this project? | ||
| A: Use a HEREDOC to ensure proper formatting: | ||
| ```bash | ||
| git commit -m "$(cat <<'EOF' | ||
| Commit message here. | ||
|
|
||
| 🤖 Generated with [Claude Code](https://claude.ai/code) | ||
|
|
||
| Co-Authored-By: Claude <noreply@anthropic.com> | ||
| EOF | ||
| )" | ||
| ``` | ||
|
|
||
| ### Q: What commands should you run before considering a task complete? | ||
| A: Always run: | ||
| 1. `pnpm test run <relevant-test-files>` - Run tests | ||
| 2. `pnpm lint` - Check for linting errors | ||
| 3. `pnpm typecheck` - Check for TypeScript errors | ||
|
|
||
| ## Common Pitfalls | ||
|
|
||
| ### Q: Why might imports get removed after running lint --fix? | ||
| A: ESLint may remove "unused" imports. Always verify your changes after auto-fixing, especially if you're using imports in a way ESLint doesn't recognize (like in test expectations). | ||
|
|
||
| ### Q: What's a common linting error in test files? | ||
| A: Missing newline at end of file. ESLint requires files to end with a newline character. | ||
|
|
||
| ### Q: How do you handle TypeScript errors about missing exports? | ||
| A: Double-check that you're only importing what's actually exported from a module. The error "Module declares 'X' locally, but it is not exported" means you're trying to import something that isn't exported. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| { | ||
| "$schema": "https://json.schemastore.org/claude-code-settings.json", | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(pnpm typecheck:*)", | ||
| "Bash(pnpm test:*)", | ||
| "Bash(pnpm lint:*)", | ||
| "Bash(find:*)", | ||
| "Bash(ls:*)", | ||
| "Bash(pnpm codegen)", | ||
| "Bash(pnpm vitest run:*)", | ||
| "Bash(pnpm eslint:*)" | ||
| ], | ||
| "deny": [] | ||
| }, | ||
| "includeCoAuthoredBy": false, | ||
| "hooks": { | ||
| "PostToolUse": [ | ||
| { | ||
| "matcher": "Edit|MultiEdit|Write", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "pnpm run lint --fix" | ||
|
N2D4 marked this conversation as resolved.
|
||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Escape literal dots and anchor the regex to avoid over-matching
As written, the dots in hostnames will act as “any character” in regex and may over-match. Also, add start/end anchors. Consider escaping the pattern before wildcard replacement.
Apply this diff to the snippet to properly escape, anchor, and handle both single and double wildcards:
🤖 Prompt for AI Agents