Skip to content
This repository was archived by the owner on Feb 12, 2026. It is now read-only.

Makes redirect route dynamic - #36

Merged
kauanrfontana merged 4 commits into
developfrom
refactor/auth-endpoints
Dec 10, 2025
Merged

kauanrfontana merged 4 commits into
developfrom
refactor/auth-endpoints

Conversation

@kauanrfontana

@kauanrfontana kauanrfontana commented Dec 5, 2025 •

Copy link
Copy Markdown
Member

Descrição

Torna rota de redirect dinâmica .

Tipos de Mudanças

  • fix: correção de bug
  • feat: nova funcionalidade
  • docs: mudanças na documentação
  • chore: mudanças no processo de build ou ferramentas auxiliares
  • refactor: refatoração de código existente
  • test: adição ou modificação de testes
  • perf: melhorias de performance
  • ci: mudanças em arquivos de configuração de CI/CD

Como o teste foi realizado?

  • Testes unitários (sucesso e falha)
  • Teste em máquina local
  • Teste em ambiente de homologação
  • Teste em ambiente de produção

Checklist:

  • Meu código segue as diretrizes de estilo deste projeto
  • Eu realizei uma autoavaliação do meu próprio código
  • Eu comentei meu código, especialmente em áreas de difícil compreensão
  • Eu adicionei documentação conforme necessário
  • Minhas mudanças não geram novos avisos de compilação (warnings)
  • Eu adicionei testes para cobrir minhas mudanças
  • Todos os testes novos e existentes passam localmente com minhas mudanças
  • Qualquer dependência de terceiros foi atualizada e documentada

Summary by CodeRabbit

  • New Features

    • OAuth flow now supports custom redirect destinations so users return to their intended page after Google login.
    • New endpoints to check authentication status and to fetch the current authenticated user.
  • Bug Fixes

    • Safer handling of return destinations with validation, base64-encoded state, and fallback to a default page on errors.
  • Tests

    • Added comprehensive tests for OAuth redirect scenarios, validation, and error handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 5, 2025 •

Copy link
Copy Markdown

Walkthrough

Adds safe redirect state handling to Google OAuth: accepts a redirectTo query (validated as a relative path), base64-encodes it into OAuth state on initiation, then base64-decodes, validates, and redirects to the decoded path (or falls back) in the callback; also adds /me and /status endpoints.

Changes

Cohort / File(s) Summary
OAuth state handling & endpoints
src/routes/auth.js
Replaces static Google OAuth flow with dynamic /auth/google that accepts redirectTo, validates it (must start with / and not //), base64-encodes into state; callback /auth/google/callback decodes state, validates decoded path, redirects to decoded path or /default, uses /login on failure; adds GET /me and GET /status.
OAuth tests & mocks
tests/routes/auth.test.js
Adds mocked passport behavior and tests for initiation (state empty vs base64 when redirectTo present), callback redirections for valid encoded state, missing/invalid state fallback, and decoding error handling.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant Passport
    participant Google

    rect rgb(240,248,255)
    Note over Client,Server: Initiate OAuth
    Client->>Server: GET /api/v1/auth/google?redirectTo=/path
    Server->>Server: validate /path (starts with '/', not '//')
    Server->>Server: base64-encode -> state
    Server->>Passport: passport.authenticate(..., state)
    Passport->>Google: Redirect to Google with state
    end

    rect rgb(245,255,240)
    Note over Google,Server: Callback after consent
    Google->>Server: GET /auth/google/callback?state=<base64>&code=...
    Server->>Server: try base64-decode(state)
    Server->>Server: validate decoded path (safe relative)
    alt decoded path valid
        Server-->>Client: 302 Redirect -> decoded path
    else decode fails or invalid
        Server-->>Client: 302 Redirect -> /default (or /login on failure)
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Inspect src/routes/auth.js for correct base64 encode/decode, error handling, and logging.
  • Verify safe-relative-path validation covers edge cases (e.g., //, URL-encoded values).
  • Confirm tests in tests/routes/auth.test.js accurately mock passport and cover success/failure branches.

Possibly related PRs

Suggested reviewers

  • vlghpp

Poem

🐰
I hid the path in tiny code,
A curled-up state for every road,
Base64 hops, a careful leap,
Back home I bounce — no data to keep,
Thump-thump, redirect—quietly to sleep 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Makes redirect route dynamic' clearly summarizes the main change: converting a static OAuth redirect route to a dynamic implementation based on query parameters.
Description check ✅ Passed The pull request description follows the required template with most sections completed, including a brief description in Portuguese, marked change types (feat and test), testing approaches (unit tests and local testing), and a mostly completed checklist. Some non-critical items remain unchecked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/auth-endpoints

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/routes/auth.test.js (2)

8-32: Refactor to avoid duplicating production logic in test mocks.

The createMockedAuthRoutes function duplicates the exact redirect logic from src/routes/auth.js. This creates maintenance burden and defeats the purpose of tests catching regressions—when production code is fixed, tests must also be updated.

Consider these alternatives:

  • Option 1: Import and test the actual routes from authRoutes, mocking only passport.authenticate at the module level
  • Option 2: Extract the state decoding/validation logic into a separate testable function that both production and tests can use

Example for Option 2:

// In src/utils/redirectValidator.js
export const getValidRedirectUrl = (state, fallback = '/default') => {
  let finalUrl = fallback;
  
  if (state) {
    try {
      const decoded = Buffer.from(state, 'base64').toString('utf-8');
      if (decoded && decoded.startsWith('/') && !decoded.startsWith('//')) {
        finalUrl = decoded;
      }
    } catch (err) {
      console.error('Error decoding state:', err);
    }
  }
  
  return finalUrl;
};

Then use this function in both production routes and test mocks, and add unit tests specifically for getValidRedirectUrl.


245-403: Add security test cases for open redirect vulnerability.

The current implementation accepts any URL starting with http or / (lines 31-32 in src/routes/auth.js), creating an open redirect vulnerability. The tests do not validate security boundaries. Critical missing test cases:

  • External URLs (e.g., https://evil.com) should be rejected, not redirected to
  • Protocol-relative URLs (e.g., //evil.com) should be rejected
  • Only relative paths (e.g., /api/v1/events) should be accepted

Add these security-focused test cases:

describe('Security - Open Redirect Prevention', () => {
  it('deve rejeitar redirect para domínio externo via https', async () => {
    const app = createTestApp();
    const maliciousUrl = 'https://evil.com/phishing';
    const encodedState = Buffer.from(maliciousUrl).toString('base64');
    
    const mockAuthenticate = (req, res, next) => {
      req.user = { /* mock user */ };
      next();
    };
    
    const mockedRoutes = createMockedAuthRoutes(mockAuthenticate);
    app.use('/api/v1', mockedRoutes);
    
    const response = await request(app)
      .get(`/api/v1/auth/google/callback?state=${encodedState}`);
    
    expect(response.status).toBe(302);
    expect(response.headers.location).toBe('/default');
  });
  
  it('deve rejeitar protocol-relative URL (//evil.com)', async () => {
    const app = createTestApp();
    const maliciousUrl = '//evil.com/phishing';
    const encodedState = Buffer.from(maliciousUrl).toString('base64');
    
    const mockAuthenticate = (req, res, next) => {
      req.user = { /* mock user */ };
      next();
    };
    
    const mockedRoutes = createMockedAuthRoutes(mockAuthenticate);
    app.use('/api/v1', mockedRoutes);
    
    const response = await request(app)
      .get(`/api/v1/auth/google/callback?state=${encodedState}`);
    
    expect(response.status).toBe(302);
    expect(response.headers.location).toBe('/default');
  });
  
  it('deve aceitar apenas paths relativos válidos', async () => {
    const app = createTestApp();
    const validPath = '/api/v1/events';
    const encodedState = Buffer.from(validPath).toString('base64');
    
    const mockAuthenticate = (req, res, next) => {
      req.user = { /* mock user */ };
      next();
    };
    
    const mockedRoutes = createMockedAuthRoutes(mockAuthenticate);
    app.use('/api/v1', mockedRoutes);
    
    const response = await request(app)
      .get(`/api/v1/auth/google/callback?state=${encodedState}`);
    
    expect(response.status).toBe(302);
    expect(response.headers.location).toBe(validPath);
  });
});

Note: The test at lines 84-109 expects a redirect to https://extensaoads2.sj.ifsc.edu.br/api/v1/events, which demonstrates the current vulnerability. Update the implementation to reject absolute URLs before adding these tests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ebe832 and cbe2c62.

📒 Files selected for processing (2)
  • src/routes/auth.js (1 hunks)
  • tests/routes/auth.test.js (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/routes/auth.test.js (1)
src/routes/auth.js (6)
  • router (4-4)
  • req (7-7)
  • state (10-10)
  • state (24-24)
  • finalUrl (25-25)
  • decoded (29-29)

Comment thread src/routes/auth.js
Comment thread src/routes/auth.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/routes/auth.js (1)

24-47: State decode / redirect flow looks good; mirror the query normalization here

The callback now properly restricts redirects to absolute‑relative paths (/… but not //…), which addresses the previous open‑redirect concern. To harden it against odd query shapes (multiple state params), normalize state the same way as redirectTo before decoding:

   (req, res) => {
-    // Recupera o redirectTo do state parameter
-    const state = req.query.state;
+    // Recupera o redirectTo do state parameter
+    const rawState = req.query.state;
+    const state = Array.isArray(rawState) ? rawState[0] : rawState;
     let finalUrl = '/default';
     
-    if (state) {
+    if (typeof state === 'string' && state) {
       try {
         const decoded = Buffer.from(state, 'base64').toString('utf-8');
         // Valida se o resultado é um path relativo seguro (não permite URLs externas)
         if (decoded && decoded.startsWith('/') && !decoded.startsWith('//')) {
            finalUrl = decoded;
          }
       } catch (err) {
         console.error('Error decoding state:', err);
       }
     }
 
     res.redirect(finalUrl);
   }

Functionally you keep the same redirect behavior, but avoid surprises from non‑string state values.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbe2c62 and c14dbb0.

📒 Files selected for processing (1)
  • src/routes/auth.js (1 hunks)

Comment thread src/routes/auth.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/routes/auth.js (2)

6-23: Harden path validation against traversal and injection.

The current validation correctly prevents open redirect vulnerabilities by restricting to relative paths. However, consider adding additional checks to prevent potential path traversal and injection attacks:

 router.get('/auth/google', (req, res, next) => {
   const rawRedirectTo = req.query.redirectTo;
   const redirectTo = Array.isArray(rawRedirectTo) ? rawRedirectTo[0] : rawRedirectTo;
 
   let validatedRedirect = '';
-  if (typeof redirectTo === 'string' && redirectTo.startsWith('/') && !redirectTo.startsWith('//')) {
+  if (
+    typeof redirectTo === 'string' &&
+    redirectTo.startsWith('/') &&
+    !redirectTo.startsWith('//') &&
+    !redirectTo.includes('..') &&
+    !/[\r\n\0]/.test(redirectTo)
+  ) {
     validatedRedirect = redirectTo;
   }
 
   const state = validatedRedirect
     ? Buffer.from(validatedRedirect).toString('base64')
     : '';
 
   passport.authenticate('google', {
     scope: ['profile', 'email'],
     state: state
   })(req, res, next);
 });

This prevents:

  • Path traversal sequences like /../admin
  • CRLF injection via newlines
  • Null byte injection

29-47: Apply the same hardening to callback validation.

To maintain consistency with the initiation route, apply the same additional validation checks here:

   (req, res) => {
     // Recupera o redirectTo do state parameter
     const state = req.query.state;
     let finalUrl = '/default';
     
     if (state) {
       try {
         const decoded = Buffer.from(state, 'base64').toString('utf-8');
         // Valida se o resultado é um path relativo seguro (não permite URLs externas)
-        if (decoded && decoded.startsWith('/') && !decoded.startsWith('//')) {
+        if (
+          decoded &&
+          decoded.startsWith('/') &&
+          !decoded.startsWith('//') &&
+          !decoded.includes('..') &&
+          !/[\r\n\0]/.test(decoded)
+        ) {
           finalUrl = decoded;
         }
       } catch (err) {
         console.error('Error decoding state:', err);
       }
     }
 
     res.redirect(finalUrl);
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c14dbb0 and a336740.

📒 Files selected for processing (1)
  • src/routes/auth.js (2 hunks)
🔇 Additional comments (2)
src/routes/auth.js (2)

59-71: LGTM!

The /me endpoint implementation is clean and follows best practices. Authentication check is correct, and the response provides appropriate user information.


92-102: LGTM!

The /status endpoint is well-implemented and provides a clean way for clients to check authentication state.

@kauanrfontana
kauanrfontana merged commit 0ade50e into develop Dec 10, 2025
3 checks passed
@kauanrfontana
kauanrfontana deleted the refactor/auth-endpoints branch December 10, 2025 22:48
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants