Skip to content

Staging#4

Merged
kipsang01 merged 75 commits intomainfrom
staging
Jan 6, 2026
Merged

Staging#4
kipsang01 merged 75 commits intomainfrom
staging

Conversation

@kipsang01
Copy link
Collaborator

@kipsang01 kipsang01 commented Dec 11, 2025

User description

What kind of change does this PR introduce?

eg: Bug fix, feature, docs update, ...

Why was this change needed?

Please link to related issues when possible, and explain WHY you changed things, not WHAT you changed.

Other information:

eg: Did you discuss this change with anybody before working on it (not required, but can be a good idea for bigger changes). Any plans for the future, etc?

Checklist:

Put a "X" in the boxes below to indicate you have followed the checklist;

  • I have read the CONTRIBUTING guide.
  • I checked that there were not similar issues or PRs already open for this.
  • This PR fixes just ONE issue (do not include multiple issues or types of change in the same PR) For example, don't try and fix a UI issue and include new dependencies in the same PR.

PR Type

Enhancement, Tests


Description

  • Add FusionAuth SSO provider integration with OAuth2 support

  • Implement dynamic SSO endpoints with redirect validation and error handling

  • Add Elastic APM monitoring for backend, workers, and frontend (RUM)

  • Add GitHub Actions workflow for Docker image building and pushing

  • Enhance exception filter with APM error capture and improved error responses


Diagram Walkthrough

flowchart LR
  A["FusionAuth OAuth2"] -->|"code/assertion"| B["SSO Endpoints<br/>GET/POST /sso/:provider"]
  B -->|"validate & exchange"| C["FusionAuth Provider"]
  C -->|"get user info"| D["Auth Service<br/>sso method"]
  D -->|"JIT provision"| E["User/Org Creation"]
  E -->|"generate JWT"| F["Auth Cookie<br/>Response"]
  G["Elastic APM<br/>Backend/Workers"] -->|"capture errors"| H["Exception Filter"]
  I["Elastic APM RUM<br/>Frontend"] -->|"track transactions"| J["Browser Monitoring"]
  K["GitHub Actions<br/>Workflow"] -->|"build & push"| L["Docker Hub<br/>Multi-arch Images"]
Loading

File Walkthrough

Relevant files
Enhancement
12 files
auth.controller.ts
Add dynamic SSO endpoints with provider support                   
+127/-0 
fusionauth.provider.ts
Implement FusionAuth OAuth2 provider integration                 
+64/-0   
providers.factory.ts
Register FusionAuth provider in factory                                   
+3/-0     
auth.service.ts
Add SSO assertion handler with JIT provisioning                   
+47/-0   
exception.filter.ts
Enhance exception filter with APM logging                               
+51/-5   
apm.init.ts
Initialize Elastic APM for backend service                             
+19/-0   
apm.init.ts
Initialize Elastic APM for workers service                             
+17/-0   
apm.rum.ts
Initialize Elastic APM RUM for frontend                                   
+24/-0   
instrumentation.ts
Add APM RUM initialization to frontend                                     
+10/-4   
main.ts
Import APM initialization early in startup                             
+1/-0     
main.ts
Import APM initialization early in startup                             
+1/-0     
schema.prisma
Add FUSIONAUTH provider enum value                                             
+1/-0     
Configuration changes
2 files
build-and-push-dockerhub.yml
Add Docker Hub image build and push workflow                         
+86/-0   
pr-docker-build.yml
Disable automatic PR image builds                                               
+4/-4     
Dependencies
3 files
package.json
Add Elastic APM dependencies                                                         
+2/-0     
package.json
Add Elastic APM RUM dependency                                                     
+4/-1     
pnpm-lock.yaml
Update lock file with APM dependencies                                     
+439/-1 
Documentation
1 files
.env.example
Add Elastic APM configuration examples                                     
+13/-0   

@qodo-code-review
Copy link

qodo-code-review bot commented Dec 11, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Open redirect risk

Description: The redirect validation only compares hostnames to an allowed domain and constructs the
URL with a base, which may still allow open redirects to other subdomains or crafted URLs
(e.g., different scheme, port, or internationalized domain tricks), potentially enabling
phishing or token leakage; restrict redirects to a vetted allowlist of full paths or exact
origins and avoid accepting user-supplied absolute URLs.
auth.controller.ts [377-384]

Referred Code
if (redirect) {
  const allowedDomain = new URL(process.env.FRONTEND_URL!).hostname;
  const redirectUrl = new URL(redirect, process.env.FRONTEND_URL!);
  if (redirectUrl.hostname !== allowedDomain) {
    return response.status(400).send('Invalid redirect domain');
  }
  return response.redirect(302, redirectUrl.toString());
}
Insecure cookie flags

Description: Setting the auth and showorg cookies without secure, httpOnly, and SameSite=None when
NOT_SECURED is set may expose authentication cookies via JavaScript access and insecure
transport in non-production deployments, increasing risk of token theft; ensure secure
flags are set consistently or block SSO in insecure modes.
auth.controller.ts [343-371]

Referred Code
response.cookie('auth', jwt, {
  domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
  ...(!process.env.NOT_SECURED
    ? {
        secure: true,
        httpOnly: true,
        sameSite: 'none',
      }
    : {}),
  expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
});

if (process.env.NOT_SECURED) {
  response.header('auth', jwt);
}

if (typeof addedOrg !== 'boolean' && addedOrg?.organizationId) {
  response.cookie('showorg', addedOrg.organizationId, {
    domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),
    ...(!process.env.NOT_SECURED
      ? {


 ... (clipped 8 lines)
Insecure OAuth endpoint

Description: The FusionAuth token exchange uses HTTP Basic auth with client secret read from
environment but there is no explicit TLS enforcement for issuer URLs, so a misconfigured
FUSION_AUTH_BASE_URL using http could leak credentials; validate that the issuer uses
https and reject non-TLS endpoints.
fusionauth.provider.ts [26-31]

Referred Code
const headers: Record<string, string> = {
  'content-type': 'application/x-www-form-urlencoded',
  Accept: 'application/json',
  Authorization: `Basic ${Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64')}`,
};
Sensitive data logging

Description: Capturing and sending full request headers to APM (headers: request?.headers) can leak
sensitive data such as Authorization or cookies to observability backends; sanitize or
omit sensitive headers before reporting.
exception.filter.ts [45-67]

Referred Code
// Capture handled server errors (500-level) and optionally 4xx if desired
try {
  if (apm && typeof apm.captureError === 'function') {
    const ctxInfo: any = {
      url: request?.originalUrl || request?.url,
      method: request?.method,
      headers: request?.headers,
    };

    if (exception instanceof Error) {
      apm.captureError(exception, { custom: { http: ctxInfo, status } });
    } else {
      apm.captureError(new Error(JSON.stringify(exception)), { custom: { http: ctxInfo, status } });
    }
  }
} catch (e) {
  // ignore any issues with the APM capture
}

if (exception instanceof HttpException) {
  const message = (exception as any).message || 'error';


 ... (clipped 2 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit log: The new SSO login endpoints perform authentication but do not add explicit audit logging
of the critical user login event with user identity, timestamp, action, and outcome.

Referred Code
@Get('/sso/:provider')
async ssoCallbackGet(
  @Param('provider') provider: string,
  @Query('code') code: string,
  @Query('redirect') redirect: string,
  @Res({ passthrough: false }) response: Response,
  @RealIP() ip: string,
  @UserAgent() userAgent: string
) {
  return this.handleSso({
    provider,
    token: code,
    response,
    ip,
    userAgent,
    redirect: redirect || process.env.FRONTEND_URL || '/',
  });
}

@Post('/sso/:provider')
async ssoCallbackPost(


 ... (clipped 104 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Error detail leakage: Token exchange errors are returned with upstream response text which may expose internal
provider details and are later surfaced via controller responses, lacking normalization
and potential sensitive data scrubbing.

Referred Code
if (!res.ok) {
  throw new Error(`Token exchange failed: ${await res.text()}`);
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Detailed error surfaced: The SSO handler returns e.message to clients when redirect is absent which can leak
internal details instead of a generic user-facing error.

Referred Code
  return response.status(400).send(e.message || 'SSO failed');
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
PII in APM context: Exception filter captures and forwards full request headers to APM which may include
sensitive tokens or PII, lacking explicit redaction.

Referred Code
  method: request?.method,
  headers: request?.headers,
};

if (exception instanceof Error) {
  apm.captureError(exception, { custom: { http: ctxInfo, status } });
} else {
  apm.captureError(new Error(JSON.stringify(exception)), { custom: { http: ctxInfo, status } });
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Redirect validation: Redirect URL validation only compares hostname against a single allowed domain and may
allow mixed-scheme or crafted subpaths; additional checks like protocol enforcement and
exact origin matching are not shown.

Referred Code
if (redirect) {
  const allowedDomain = new URL(process.env.FRONTEND_URL!).hostname;
  const redirectUrl = new URL(redirect, process.env.FRONTEND_URL!);
  if (redirectUrl.hostname !== allowedDomain) {
    return response.status(400).send('Invalid redirect domain');
  }
  return response.redirect(302, redirectUrl.toString());
}

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Dec 11, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consolidate SSO and OAuth endpoints

The new /sso/:provider endpoints should be merged with the existing
/oauth/:provider endpoints. This will unify the authentication flow, reduce code
duplication, and simplify the overall architecture.

Examples:

apps/backend/src/api/routes/auth.controller.ts [201-270]
  @Get('/oauth/:provider')
  async oauthLink(@Param('provider') provider: string, @Query() query: any) {
    return this._authService.oauthLink(provider, query);
  }

  @Post('/activate')
  async activate(
    @Body('code') code: string,
    @Res({ passthrough: false }) response: Response
  ) {

 ... (clipped 60 lines)
apps/backend/src/api/routes/auth.controller.ts [272-397]
  @Get('/sso/:provider')
  async ssoCallbackGet(
    @Param('provider') provider: string,
    @Query('code') code: string,
    @Query('redirect') redirect: string,
    @Res({ passthrough: false }) response: Response,
    @RealIP() ip: string,
    @UserAgent() userAgent: string
  ) {
    return this.handleSso({

 ... (clipped 116 lines)

Solution Walkthrough:

Before:

// auth.controller.ts
@Controller('/auth')
export class AuthController {
  // Existing flow for providers like GitHub/Google
  @Post('/oauth/:provider/exists')
  async oauthExists(provider, code) {
    // Calls _authService.checkExists
    // Two-step: returns a token if user is new, frontend completes registration
  }

  // New flow for SSO providers like FusionAuth
  @Get('/sso/:provider')
  async ssoCallbackGet(provider, code, redirect) {
    // Calls this.handleSso
  }

  @Post('/sso/:provider')
  async ssoCallbackPost(provider, code, redirect) {
    // Calls this.handleSso
  }

  private async handleSso(...) {
    // Calls _authService.sso for Just-In-Time provisioning
  }
}

After:

// auth.controller.ts
@Controller('/auth')
export class AuthController {
  // A single, unified callback endpoint for all providers
  @Get('/callback/:provider')
  async handleProviderCallback(provider, code, redirect) {
    // Determines flow based on provider type or config
    const isSsoFlow = provider === 'FUSIONAUTH'; // or check a config flag

    if (isSsoFlow) {
      // Calls _authService.sso for Just-In-Time provisioning
      // ... logic from handleSso
    } else {
      // Calls _authService.checkExists for two-step registration
      // ... logic from oauthExists
    }
    // ... unified cookie setting and response handling
  }

  // The old /oauth/:provider/exists and new /sso/:provider endpoints are removed.
}
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a significant architectural issue where new sso endpoints duplicate the purpose of existing oauth endpoints, leading to code redundancy and increased complexity. Merging them would create a more unified and maintainable authentication flow.

Medium
Security
Fix open redirect security vulnerability

To prevent an open redirect vulnerability, add a check to ensure the redirect
parameter is a relative path and does not start with // or contain .. before
constructing the final URL.

apps/backend/src/api/routes/auth.controller.ts [377-384]

 if (redirect) {
+  if (redirect.startsWith('//') || redirect.includes('..')) {
+    return response.status(400).send('Invalid redirect path');
+  }
+  const redirectUrl = new URL(redirect, process.env.FRONTEND_URL!);
   const allowedDomain = new URL(process.env.FRONTEND_URL!).hostname;
-  const redirectUrl = new URL(redirect, process.env.FRONTEND_URL!);
   if (redirectUrl.hostname !== allowedDomain) {
     return response.status(400).send('Invalid redirect domain');
   }
   return response.redirect(302, redirectUrl.toString());
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential open redirect vulnerability and proposes a defense-in-depth fix by blocking protocol-relative URLs, which is a good security practice even if the current hostname check seems sufficient.

Medium
Avoid leaking sensitive error details

To prevent leaking sensitive information, log the detailed error from the
FusionAuth token exchange on the server and throw a generic error message to the
client.

apps/backend/src/services/auth/providers/fusionauth.provider.ts [32-45]

 const res = await fetch(`${this.issuer}/oauth2/token`, {
   method: 'POST',
   headers,
   body: new URLSearchParams({
     grant_type: 'authorization_code',
     client_id: this.clientId,
     redirect_uri: this.redirectUri,
     code: codeOrToken,
   }).toString(),
 });
 
 if (!res.ok) {
-  throw new Error(`Token exchange failed: ${await res.text()}`);
+  const errorBody = await res.text();
+  console.error('FusionAuth token exchange failed:', errorBody);
+  throw new Error('Token exchange failed');
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This is a valid security suggestion that prevents leaking potentially sensitive error details from an external service to the client, which is a critical best practice for secure error handling.

Medium
Prevent leaking sensitive error details

Prevent leaking sensitive error details by modifying the exception filter to
return a generic "Internal server error" message for all 5xx-level
HttpExceptions.

libraries/nestjs-libraries/src/services/exception.filter.ts [64-69]

 if (exception instanceof HttpException) {
+  if (status >= 500) {
+    console.error('Internal server error:', exception);
+    return response.status(status).json({ message: 'Internal server error' });
+  }
   const message = (exception as any).message || 'error';
   return response.status(status).json({ message });
 }
 
 return response.status(500).json({ message: 'Internal server error' });
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a security risk of leaking internal error details and provides a robust solution by filtering 5xx errors to return a generic message, which is a crucial security hardening measure.

Medium
General
Improve provider validation with type-safety

Improve SSO provider validation by using a case-insensitive map for converting
the provider string to its corresponding enum value, enhancing type safety and
clarity.

apps/backend/src/api/routes/auth.controller.ts [327-330]

-const providerValue = Provider[provider?.toUpperCase() as keyof typeof Provider];
+const providerMap: { [key: string]: Provider } = {
+  github: Provider.GITHUB,
+  google: Provider.GOOGLE,
+  farcaster: Provider.FARCASTER,
+  wallet: Provider.WALLET,
+  fusionauth: Provider.FUSIONAUTH,
+  generic: Provider.GENERIC,
+};
+
+const providerValue = providerMap[provider?.toLowerCase()];
 if (!providerValue) {
   return response.status(400).send('Unsupported provider');
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion improves code robustness and readability by replacing a potentially fragile string-to-enum conversion with a safer, case-insensitive map lookup, making the provider validation more explicit.

Low
  • Update

adambkovacs and others added 13 commits December 14, 2025 21:21
Add --pull and --no-cache flags to docker buildx build to ensure:
- Base image (node:22.20-alpine) is always pulled fresh
- No cached layers from previous builds are used

This fixes issue gitroomhq#1079 where published images contained outdated
Node.js v20.18.x instead of the expected v22.20.x from Dockerfile.dev,
causing Prisma 7 compatibility failures.

Fixes gitroomhq#1079
@kipsang01 kipsang01 merged commit d8bf428 into main Jan 6, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants