Skip to content

Conversation

@mkczarkowski
Copy link
Collaborator

Refactored auth API routes to use reusable guards and utilities, eliminating ~180
lines of duplicate code and establishing consistent patterns for feature flags,
captcha verification, and error responses.

Changes

New Infrastructure

  • src/pages/api/guards/ - Composable route guards
    • withFeatureFlag() - Feature flag gating
    • withCaptcha() - Automated captcha verification
    • withAuth() - Authentication & admin role checking
  • src/pages/api/utils/ - Standardized utilities
    • apiResponse.ts - Consistent response helpers (success, error, validation,
      etc.)
    • supabaseHelpers.ts - Supabase client creation & request utilities

Refactored Routes

  • auth/reset-password.ts - 58 → 38 lines (-34%)
  • auth/resend-verification.ts - 156 → 127 lines (-19%)
  • auth/login.ts - 129 → 102 lines (-21%)
  • auth/signup.ts - 314 → 261 lines (-17%)
  • auth/update-password.ts - 102 → 73 lines (-28%)

Example: Before & After

// Before: ~50 lines of boilerplate
export const POST: APIRoute = async ({ request, cookies }) => {
if (!isFeatureEnabled('auth')) { /* ... / }
const { email, captchaToken } = await request.json();
const captchaResult = await verifyCaptcha(/
... /);
if (!captchaResult.success) { /
... */ }
// ... actual logic
};

// After: 3 lines of composition
export const POST: APIRoute = withFeatureFlag('auth',
withCaptcha(async ({ body }) => {
return successResponse({ message: 'Success' });
})
);

Benefits

  • ✅ DRY principle applied - eliminated duplicate feature flag/captcha/error
    handling
  • ✅ Type-safe guard composition with full TypeScript support
  • ✅ Consistent error responses across all routes
  • ✅ Easier to add cross-cutting concerns (logging, rate limiting, etc.)
  • ✅ Better separation of concerns

Testing

  • ✅ All 185 tests passing
  • ✅ Linting successful
  • ✅ Build successful
  • ✅ No breaking changes to API contracts

@claude
Copy link

claude bot commented Oct 23, 2025

Claude finished @mkczarkowski's task —— View job


Code Review Complete ✅

Reviewing PR: refactor: extract and reuse helpers in api

Tasks

  • Read project context files
  • Review new guard implementations (withFeatureFlag, withCaptcha, withAuth)
  • Review utility functions (apiResponse, supabaseHelpers)
  • Review refactored auth routes
  • Analyze security implications
  • Check for type safety and error handling
  • Provide comprehensive feedback

Summary

This is an excellent refactoring that significantly improves code quality and maintainability. The extraction of guards and utilities follows solid software engineering principles and eliminates substantial code duplication. The PR delivers on its promise of removing ~180 lines of boilerplate while maintaining (and in some cases improving) functionality.

Strengths ✅

  1. DRY Principle Applied Effectively: The new guards (withFeatureFlag, withCaptcha, withAuth) eliminate massive amounts of duplicate code across auth routes
  2. Type Safety: Good use of TypeScript generics in withCaptcha to provide type-safe body parsing
  3. Consistent Error Responses: The apiResponse.ts utilities ensure uniform error handling across all API routes
  4. Composability: Guards can be easily composed together (e.g., withFeatureFlag('auth', withCaptcha(handler)))
  5. Security Best Practices: Maintained security features like rate limiting, captcha verification, and proper error handling
  6. Graceful Degradation: Excellent handling of missing database functions with fallback behavior and clear logging
  7. Documentation: Good JSDoc comments explaining usage and examples

Areas for Improvement 🔍

I've left 7 inline comments on specific files with detailed recommendations. Key issues:

Critical Issues

  • Error Handling in withCaptcha (src/pages/api/guards/withCaptcha.ts:99): Unhandled exceptions will bubble up as 500 errors instead of proper error responses
  • Type Safety in successResponse (src/pages/api/utils/apiResponse.ts:26): Function accepts any data structure, risking inconsistent API responses

Security Considerations

  • Captcha Error Exposure (src/pages/api/guards/withCaptcha.ts:74): Internal Cloudflare error codes exposed to end users in production
  • Client IP Fallback (src/pages/api/utils/supabaseHelpers.ts:43): Empty string fallback could cause issues; should return null and include fallback headers for local dev

Code Quality Suggestions

  • Complex Logic Extraction (src/pages/api/auth/login.ts:31): The auto-resend verification logic (~60 lines) could be extracted into a reusable helper
  • Silent Invite Failure (signup.ts): Failed invite redemption after successful signup is only logged, not communicated to user

Architecture Assessment

New Infrastructure:

src/pages/api/
├── guards/          # ✅ Well-designed composable guards
│   ├── withAuth.ts
│   ├── withCaptcha.ts
│   └── withFeatureFlag.ts
└── utils/           # ✅ Clean utility modules
    ├── apiResponse.ts
    └── supabaseHelpers.ts

The guard pattern is implemented correctly and follows the middleware composition pattern common in Node.js frameworks. The separation of concerns is clear:

  • Guards: Authentication, authorization, and validation concerns
  • Utils: Response formatting and client creation
  • Routes: Business logic only

Testing & Quality Assurance

✅ All 185 tests passing
✅ Linting successful
✅ Build successful
✅ No breaking changes to API contracts

This gives high confidence in the refactoring.

Recommendations

Before Merging:

  1. Address the error handling issue in withCaptcha (see inline comment)
  2. Consider the getClientIp fallback improvement for better local dev experience

Future Improvements:

  1. Extract the complex email resend logic from login.ts into a shared helper
  2. Add unit tests specifically for the new guards and utilities
  3. Consider adding a warnings field to success responses for non-blocking errors (like failed invite redemption)
  4. Improve type safety in successResponse to enforce consistent response structures

Performance Impact

Positive: The refactoring should have minimal to no performance impact. The guard composition adds negligible overhead compared to the previous inline checks.

Metrics

  • Lines removed: 497
  • Lines added: 757
  • Net change: +260 (mostly due to new reusable infrastructure)
  • Code duplication eliminated: ~180 lines
  • Files refactored: 5 auth routes
  • New infrastructure files: 5

The net positive line count is expected and justified - you're building reusable infrastructure that will pay dividends as more routes are added.


Verdict: ✅ Approve with Minor Suggestions

This refactoring significantly improves the codebase. The identified issues are minor and mostly suggestions for further improvement. The PR can be merged after addressing the critical error handling issue in withCaptcha.

Great work! This sets an excellent foundation for future API development.


}
throw err;
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error Handling Issue: The catch block only handles SyntaxError for JSON parsing, but then re-throws other errors. This means any other error (e.g., network issues, captcha service errors) will bubble up as an unhandled exception and return a 500 error without proper error formatting.

Recommendation: Catch all errors and return a proper error response:

} catch (err) {
  if (err instanceof SyntaxError) {
    return validationError('Invalid request body');
  }
  console.error('Captcha verification error:', err);
  return serverError('An unexpected error occurred during captcha verification');
}

requestorIp,
);

if (!captchaResult.success) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Consideration: The error response exposes internal error codes from Cloudflare Turnstile. While this can be helpful for debugging, consider whether these error-codes should be exposed to end users in production.

Recommendation: Only include errorCodes in development mode:

if (!captchaResult.success) {
  return errorResponse('Security verification failed. Please try again.', 400, {
    type: 'captcha_failed',
    ...(import.meta.env.DEV && { errorCodes: captchaResult['error-codes'] }),
  });
}

* @param data - The data to return
* @param status - HTTP status code (default: 200)
*/
export function successResponse<T = unknown>(data: T, status: number = 200): Response {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Safety Issue: The successResponse function accepts any data type and doesn't validate the structure. This could lead to inconsistent API responses.

Recommendation: Consider constraining the type to match ApiSuccessResponse:

export function successResponse<T = unknown>(
  data: ApiSuccessResponse<T> | T, 
  status: number = 200
): Response {
  // If data doesn't have the expected shape, wrap it
  const responseData = typeof data === 'object' && data !== null && ('data' in data || 'message' in data)
    ? data
    : { data };
  
  return new Response(JSON.stringify(responseData), {
    status,
    headers: { 'Content-Type': 'application/json' },
  });
}

This ensures all success responses have a consistent structure.

* Extract the client IP address from request headers
* Checks Cloudflare's cf-connecting-ip header first
*/
export function getClientIp(request: Request): string {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Issue: getClientIp returns an empty string if the cf-connecting-ip header is not present. This could cause issues downstream if the calling code expects a valid IP or null.

Recommendation: Return null or a more explicit default:

export function getClientIp(request: Request): string | null {
  return request.headers.get('cf-connecting-ip') || null;
}

Also, consider adding fallback headers for non-Cloudflare environments (useful for local development):

export function getClientIp(request: Request): string | null {
  return (
    request.headers.get('cf-connecting-ip') ||
    request.headers.get('x-forwarded-for')?.split(',')[0] ||
    request.headers.get('x-real-ip') ||
    null
  );
}

const requestorIp = getClientIp(request);

// Check rate limit first
const { data: rateLimitResult, error: rateLimitCheckError } = await supabase.rpc(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality: Excellent error handling! The automatic verification email resend logic when login fails due to unconfirmed email is a great UX improvement. However, this logic is quite complex (60+ lines).

Recommendation: Consider extracting this into a reusable helper function like handleUnconfirmedEmail() in src/pages/api/utils/ to:

  1. Reduce complexity in the route handler
  2. Allow reuse if other endpoints need similar logic
  3. Make unit testing easier

@github-actions
Copy link

✅ All checks have passed successfully!

  • Lint: ✅
  • Unit Tests: ✅
  • E2E Tests: ✅

Coverage reports have been uploaded as artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants