feat(auth): prompt to protect resources during auth generation#22
feat(auth): prompt to protect resources during auth generation#22
Conversation
When running `lvt gen auth`, users are now prompted to select which existing resources should be protected with authentication. This makes it easier to set up auth protection without manual code modifications. Changes: - Add interactive prompt after auth generation to show available resources - Allow selection by numbers, 'all', or 'none' - Add ProtectResources() function to wrap handlers with RequireAuth - Add authController creation if not already present in main.go - Ensure idempotent behavior (no duplicates on re-run) - Add comprehensive tests for resource protection feature 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code Review - PR #22I've reviewed the changes for adding interactive resource protection during auth generation. Overall, this is a well-implemented feature with good test coverage. Below are my findings: ✅ Strengths
🔍 Issues & Concerns1. Potential Regex Escaping Issue (Medium Priority)File: internal/generator/auth.go:665 The regex pattern uses pattern := fmt.Sprintf(`http\.Handle\("%s"\s*,\s*%s\.Handler\(queries\)\)`, regexp.QuoteMeta(path), regexp.QuoteMeta(packageName))Issue: If a path contains characters that are both valid URL characters AND regex metacharacters (e.g., Recommendation: Add a test case for paths with special characters: resources := []ResourceEntry{
{Name: "Posts", Path: "/posts/{id}", Type: "resource"},
}2. Silent Failure on Invalid Selections (Low Priority)File: commands/auth.go:166-170 When users enter invalid selections, the code prints warnings but continues: fmt.Printf("⚠️ Invalid selection: %d (out of range)\n", num)Issue: If a user enters "1,99" for 2 resources, only resource 1 is protected, but the user might not notice the warning. Recommendation: Consider collecting all errors and showing a summary, or asking the user to re-enter if any selections are invalid. 3. Hardcoded nil Parameters (Low Priority)File: internal/generator/auth.go:652 authController := auth.NewUserController(queries, nil, "")Issue: The hardcoded Recommendation: Add a comment explaining what these parameters are for, or consider if they should be configurable. 4. No Validation for Empty Resource Selection (Minor)File: commands/auth.go:174-186 Issue: The code only processes resources if Recommendation: If parsing resulted in warnings but no valid selections, inform the user explicitly. 🎯 Best Practices & Code QualityPositive
Minor Improvements
🔒 Security Considerations✅ No security concerns identified. The code:
⚡ Performance Considerations✅ No performance concerns. The operations are:
📝 Documentation & UXPositive:
Suggestions:
🧪 TestingTest Coverage: Excellent ✅ Suggested Additional Tests:
📋 SummaryThis is a high-quality PR with strong test coverage and good user experience design. The implementation is sound, with only minor issues that are mostly edge cases. Recommended Action: ✅ Approve with minor suggestions The identified issues are not blockers and can be addressed in follow-up PRs if needed. The core functionality is solid and well-tested. Priority of Issues
Great work! 🎉 |
There was a problem hiding this comment.
Pull request overview
This PR adds an interactive prompt during lvt gen auth to allow users to select which existing resources should be protected with authentication. After auth generation completes, if resources exist in .lvtresources, the CLI prompts the user to select resources to protect, then automatically wraps the selected handlers with authController.RequireAuth() middleware in main.go.
Key changes:
- Interactive resource selection prompt after auth generation with support for "all", "none", or comma-separated numbers
- Automatic wrapping of selected resource handlers with authentication middleware in main.go
- Comprehensive test coverage including idempotency, authController creation, and error handling
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| commands/auth.go | Adds interactive prompt after auth generation to select resources for protection, filters out Auth and Home, handles user input parsing |
| internal/generator/auth.go | Implements ProtectResources() function to modify main.go by adding authController and wrapping handlers with RequireAuth middleware |
| internal/generator/auth_test.go | Adds 4 comprehensive test cases covering handler wrapping, idempotency, authController creation, and missing main.go error handling |
| // | ||
| // Before: http.Handle("/posts", posts.Handler(queries)) | ||
| // After: http.Handle("/posts", authController.RequireAuth(posts.Handler(queries))) | ||
| func ProtectResources(projectRoot, moduleName string, resources []ResourceEntry) error { |
There was a problem hiding this comment.
The moduleName parameter is declared but never used in the function body. Consider removing this unused parameter from the function signature.
| func ProtectResources(projectRoot, moduleName string, resources []ResourceEntry) error { | |
| func ProtectResources(projectRoot, _ string, resources []ResourceEntry) error { |
| // Check if already wrapped | ||
| wrappedPattern := fmt.Sprintf(`authController\.RequireAuth\(%s\.Handler\(queries\)\)`, regexp.QuoteMeta(packageName)) | ||
| if strings.Contains(mainContent, wrappedPattern) { |
There was a problem hiding this comment.
The check for whether a handler is already wrapped only looks for the exact pattern with authController.RequireAuth. However, if someone uses a different variable name for the auth controller (e.g., ac or ctrl), this check will fail and potentially wrap the handler twice. Consider making the idempotency check more robust by using a regex pattern that looks for any variable name followed by .RequireAuth(packageName.Handler(queries)).
| // Check if already wrapped | |
| wrappedPattern := fmt.Sprintf(`authController\.RequireAuth\(%s\.Handler\(queries\)\)`, regexp.QuoteMeta(packageName)) | |
| if strings.Contains(mainContent, wrappedPattern) { | |
| // Check if already wrapped (any controller variable, e.g., authController, ac, ctrl) | |
| wrappedPattern := fmt.Sprintf(`\w+\.RequireAuth\(\s*%s\.Handler\(queries\)\s*\)`, regexp.QuoteMeta(packageName)) | |
| wrappedRe := regexp.MustCompile(wrappedPattern) | |
| if wrappedRe.MatchString(mainContent) { |
| mainContent = mainContent[:lastAuthRouteEnd+1] + authControllerCode + mainContent[lastAuthRouteEnd+1:] | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
When no auth routes are found (lastAuthRouteEnd remains 0), the authController creation code is silently skipped. This could lead to a situation where the function attempts to wrap handlers with authController.RequireAuth() but the authController variable doesn't exist, resulting in invalid Go code. Consider returning an error if no auth routes are found, or logging a warning to help with debugging.
| // Ensure we don't generate code that references a missing authController | |
| if len(resources) > 0 && !strings.Contains(mainContent, "authController :=") { | |
| return fmt.Errorf("cannot protect resources: authController not found and auth routes not detected in main.go") | |
| } |
| mainContent := string(content) | ||
|
|
||
| // Add auth controller creation if not present | ||
| if !strings.Contains(mainContent, "authController :=") { |
There was a problem hiding this comment.
The check authController := uses := which only matches short variable declarations. If the authController is declared differently (e.g., var authController = ... or authController = ...), this check will fail and create a duplicate declaration. Consider using a more flexible pattern such as checking for authController followed by any of :=, =, or searching for auth.NewUserController.
| if !strings.Contains(mainContent, "authController :=") { | |
| authControllerDeclRe := regexp.MustCompile(`authController\s*(?::=|=)`) | |
| if !authControllerDeclRe.MatchString(mainContent) && !strings.Contains(mainContent, "auth.NewUserController(") { |
When protecting resources, the auth controller now uses a console email sender by default instead of nil. This ensures magic links work out of the box in development mode (links are printed to server logs). Changes: - Add email package import when creating auth controller - Configure NewConsoleEmailSender() by default - Set baseURL from PORT environment variable - Update tests to expect email sender configuration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pull Request Review: Resource Protection During Auth GenerationSummaryThis PR adds an interactive prompt after Code Quality: ✅ ExcellentStrengths
Potential Issues & Suggestions🔴 Critical: Missing Function ReferenceLocation: baseURL := "http://localhost:" + getPort()Issue: The Impact: This will cause a compilation error in the generated main.go if Recommendation: Before inserting the auth controller code, check if
🟡 Medium: Regex Pattern RobustnessLocation: Issue: The regex pattern assumes a specific formatting style. If users format their code differently (e.g., with extra whitespace, line breaks, or different variable names), the pattern will not match. Recommendation: Consider a more flexible approach or document the formatting requirements. Alternatively, use AST parsing for more robust code modification. 🟡 Medium: Import Insertion LogicLocation: Issue: This assumes a multi-line import block with Recommendation: Check for both formats or add a comment explaining the assumption. 🟢 Minor: User Input ValidationLocation: The input parsing for comma-separated numbers is well done, but consider:
Performance Considerations: ✅ Good
Security Concerns: ✅ Good
Test Coverage: ✅ ExcellentFour comprehensive tests covering:
Recommendations SummaryMust Fix (Before Merge)
Should Fix (High Priority)
Nice to Have (Low Priority)
Overall Assessment: ⭐⭐⭐⭐ (4/5)This is a well-implemented feature with excellent test coverage and good user experience. The main concern is the The code follows the repository conventions, is well-tested, and provides a valuable feature that improves the auth generation workflow. Reviewed by: Claude Code Agent |
- Remove unused moduleName parameter (use _ instead) - Make authController declaration check more robust with regex to handle both := and = declarations - Add error when no auth routes found to prevent invalid code - Make idempotency check more robust to handle any controller variable name with .RequireAuth() - Add test for no auth routes error case 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PR Review: Resource Protection FeatureOverviewThis PR adds interactive resource protection during auth generation, allowing users to select which resources should require authentication. The implementation is well-structured with comprehensive test coverage. ✅ Strengths
🔍 Issues & Recommendations1. Unused Parameter in ProtectResources// internal/generator/auth.go:612
func ProtectResources(projectRoot, _ string, resources []ResourceEntry) error {The second parameter (module name) is unused. Either:
2. Input Validation Edge Cases// commands/auth.go:162-170
if num, err := strconv.Atoi(part); err == nil {
if num >= 1 && num <= len(protectableResources) {
selectedResources = append(selectedResources, protectableResources[num-1])
} else {
fmt.Printf("⚠️ Invalid selection: %d (out of range)\n", num)
}
}Issue: Invalid selections print warnings but don't prevent execution. Users might think their invalid choices were accepted. Recommendation: Track if any invalid selections occurred and ask user to re-enter, or at least show a summary before proceeding. 3. Potential Duplicate Resources// commands/auth.go:159-171
for _, part := range parts {
part = strings.TrimSpace(part)
if num, err := strconv.Atoi(part); err == nil {
if num >= 1 && num <= len(protectableResources) {
selectedResources = append(selectedResources, protectableResources[num-1])
}
}
}Issue: If user enters "1,1,2", resource 1 will be added twice. The regex check in ProtectResources prevents double-wrapping, but it's still wasteful. Recommendation: Use a map or check for duplicates before appending. 4. Regex Pattern Fragility// internal/generator/auth.go:687-688
pattern := fmt.Sprintf(`http\.Handle("%s"\s*,\s*%s\.Handler\(queries\)\)`,
regexp.QuoteMeta(path), regexp.QuoteMeta(packageName))Issue: This pattern is very specific and will fail if:
Recommendation: Consider using AST parsing (go/ast) for more robust code modification, or document the expected format clearly. 5. Missing Import Error Handling// internal/generator/auth.go:629-641
emailImport := `"github.com/livetemplate/lvt/pkg/email"`
if !strings.Contains(mainContent, emailImport) {
importStart := strings.Index(mainContent, "import (")
if importStart != -1 {
importEndRel := strings.Index(mainContent[importStart:], "\n)")
if importEndRel != -1 {
insertPos := importStart + importEndRel
mainContent = mainContent[:insertPos] + "\n\n\t" + emailImport + mainContent[insertPos:]
}
}
}Issue: Silently fails if import block isn't found. This could lead to compilation errors. Recommendation: Return an error if import injection fails. 6. Hard-coded Port Assumption// internal/generator/auth.go:674
baseURL := "http://localhost:" + getPort()Issue: Assumes Recommendation: Add a test case that verifies the generated code compiles, or at least document this requirement. 7. Test Coverage GapsMissing test cases for:
🛡️ Security Considerations✅ Good: Uses ⚡ Performance Considerations
🎯 Style & Best Practices✅ Good: Follows Go naming conventions 📋 Suggested Changes PriorityHigh Priority:
Medium Priority: Low Priority: ✨ Overall AssessmentThis is a solid PR that adds valuable functionality with good test coverage. The interactive UX is well-designed, and the code is generally safe and idempotent. The main concerns are around edge case handling and the fragility of string-based code modification. Recommendation: Approve with minor changes (address high-priority items). Great work on the comprehensive tests! 🎉 |
- Add error when import block is missing (fixes silent failure) - Use fallback port 8080 when getPort() function doesn't exist (prevents compilation errors in custom main.go files) - Prevent duplicate resource selections with seen map - Add tests for missing import block, getPort() fallback, and getPort() usage when available 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pull Request Review - PR #22OverviewThis PR adds an interactive feature to protect resources with authentication after running ✅ StrengthsCode Quality
Best Practices
🔍 Observations & Suggestions1. Input Validation EnhancementLocation: The duplicate prevention works well, but consider providing feedback when duplicates are detected: if !seen[num] {
seen[num] = true
selectedResources = append(selectedResources, protectableResources[num-1])
} else {
// Optional: inform user about duplicate
// fmt.Printf("ℹ️ Skipping duplicate selection: %d\n", num)
}Priority: Low (current behavior is acceptable) 2. Regex Pattern RobustnessLocation: The regex pattern assumes specific whitespace patterns. Consider making it more flexible: // Current pattern is fine, but could be more flexible with \s*
pattern := fmt.Sprintf(`http\.Handle\("%s"\s*,\s*%s\.Handler\(queries\)\)`, ...)
// Could become:
pattern := fmt.Sprintf(`http\.Handle\(\s*"%s"\s*,\s*%s\.Handler\(\s*queries\s*\)\s*\)`, ...)Priority: Low (current pattern handles typical generated code well) 3. String Manipulation ComplexityLocation: The import insertion uses string index manipulation. While it works, consider using Priority: Low (premature optimization - current approach works well) 4. Port Detection LogicLocation: The port detection with baseURLCode = `baseURL := "http://localhost:8080" // TODO: Update if your PORT differs`Priority: Low (current comment is clear enough) 5. Error ContextLocation: Excellent error message! This helps users understand what went wrong and what to do: return fmt.Errorf("no auth routes found in main.go - ensure auth was generated before protecting resources")🐛 Potential IssuesNone FoundNo bugs detected. The code handles edge cases well and includes proper validation. 🔒 Security Considerations✅ Good Security Practices
Note on File PermissionsLocation: File is written with ⚡ Performance ConsiderationsEfficient Implementation
Minor Optimization OpportunityLocation: Consider compiling regex patterns once: // Pre-compile common patterns
var authControllerDeclRe = regexp.MustCompile(`authController\s*(?::=|=)`)
// Use in function
if !authControllerDeclRe.MatchString(mainContent) { ... }Priority: Low (premature optimization for CLI command) 📋 Test Coverage AssessmentExcellent CoverageThe test suite covers all critical paths:
Test Quality
📚 DocumentationGood Documentation
SuggestionConsider adding a brief usage example in the generator package documentation or README for developers who want to understand the resource protection feature. 🎯 Overall AssessmentRating: ⭐⭐⭐⭐⭐ (5/5) This is a high-quality PR that:
Recommendation: ✅ APPROVEThe code is production-ready and demonstrates strong engineering practices. The suggestions above are minor improvements that could be addressed in future iterations if desired, but they don't block this PR. 📝 Minor Suggestions for Future Consideration
Great work! 🎉 Review generated by Claude Code PR Review |
Summary
lvt gen authto select which resources should be protectedauthController.RequireAuth()in main.goChanges
commands/auth.go: Add interactive prompt to select resources after auth generationinternal/generator/auth.go: AddProtectResources()function to update main.gointernal/generator/auth_test.go: Add comprehensive tests for resource protectionHow it works
After auth generation completes, if there are existing resources (from
.lvtresources), the CLI prompts:Test plan
TestProtectResources_WrapsHandlers- verifies handlers are wrappedTestProtectResources_Idempotent- ensures no duplicates on re-runTestProtectResources_AddsAuthController- verifies authController creationTestProtectResources_NoMainGo- handles missing main.go gracefullylvt gen authwith "all"🤖 Generated with Claude Code