Skip to content

Conversation

@wilsonrivera
Copy link
Contributor

@wilsonrivera wilsonrivera commented Aug 15, 2025

Summary by CodeRabbit

  • Bug Fixes
    • Renewal now computes expiration relative to the session’s original creation time for consistent lifetimes.
    • Issued tokens use the remaining lifetime so token expiry matches session expiry.
    • Renewal is rejected when the session’s absolute lifetime has already elapsed to prevent silent extensions.
    • Persisted session expiration now reflects the corrected calculation.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/cosmo-docs.
  • I have read the Contributors Guide.

@coderabbitai
Copy link

coderabbitai bot commented Aug 15, 2025

Walkthrough

renewSession now anchors session expiry to the session's creation time (uses userSession.createdAt.getTime()), computes absolute expiresAt, refuses renewal when that lifetime has elapsed, persists the adjusted expiresAt, and issues the JWT with maxAge equal to the remaining seconds until that absolute expiry.

Changes

Cohort / File(s) Summary of changes
Auth session renewal
controlplane/src/core/auth-utils.ts
Use userSession.createdAt.getTime() as base; compute expiresAt = base + DEFAULT_SESSION_MAX_AGE_SEC * 1000; derive remainingSeconds and throw AuthenticationError('Session expired') if <= 0; persist expiresAt (Date) on session; generate JWT with maxAgeInSeconds: remainingSeconds; add clarifying comment about expiration being relative to creation time.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch wilson/eng-7923-session-should-expire-with-keycloaks-session

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
controlplane/src/core/auth-utils.ts (1)

349-356: Align JWT maxAge with remaining fixed session lifetime

Currently the encrypted session token is minted with the full DEFAULT_SESSION_MAX_AGE_SEC, which reintroduces sliding validity for decryption even when the absolute session lifetime is shorter. Use the computed remainingSeconds instead.

-      const jwt = await encrypt<UserSession>({
-        maxAgeInSeconds: sessionExpiresIn,
+      const jwt = await encrypt<UserSession>({
+        maxAgeInSeconds: remainingSeconds,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c51be85 and 90fa45d.

📒 Files selected for processing (1)
  • controlplane/src/core/auth-utils.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
  • GitHub Check: build_push_image
  • GitHub Check: build_test

Copy link

@coderabbitai coderabbitai bot left a comment

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 (4)
controlplane/src/core/auth-utils.ts (4)

320-326: Round up TTL and capture time once to avoid off-by-one and drift

Using floor and multiple Date.now() calls can produce a 0-second TTL when only a few ms remain and introduces minor drift. Prefer capturing now once and using ceil.

Apply this diff:

-      const baseMs = userSession.createdAt?.getTime() ?? Date.now();
-      const expiresAtMs = baseMs + DEFAULT_SESSION_MAX_AGE_SEC * 1000;
-      const sessionExpiresDate = new Date(expiresAtMs);
-      const remainingSeconds = Math.max(0, Math.floor((expiresAtMs - Date.now()) / 1000));
+      const nowMs = Date.now();
+      const baseMs = userSession.createdAt?.getTime() ?? nowMs;
+      const expiresAtMs = baseMs + DEFAULT_SESSION_MAX_AGE_SEC * 1000;
+      const sessionExpiresDate = new Date(expiresAtMs);
+      const remainingSeconds = Math.max(0, Math.ceil((expiresAtMs - nowMs) / 1000));

356-358: Avoid TTL drift at issuance: recompute remaining seconds for the JWT

Between computing remainingSeconds and issuing the JWT, some time elapses (network call + DB update). Recompute just-in-time so the JWT doesn’t outlive the cookie’s expiry. Minimal change inline below. If you prefer stricter semantics, re-check and abort when the recomputed value is 0.

Apply this diff:

-        maxAgeInSeconds: remainingSeconds,
+        maxAgeInSeconds: Math.max(0, Math.ceil((sessionExpiresDate.getTime() - Date.now()) / 1000)),

291-294: Update the docstring to reflect fixed-lifetime enforcement

The behavior now also rejects renewal when the absolute session lifetime is exceeded. Reflect that to keep docs accurate.

   /**
-   * renewSession renews the user session if the access token is expired.
-   * If the refresh token is expired, an error is thrown.
+   * renewSession renews the user session if the access token is (near) expired,
+   * provided the session has not exceeded its absolute lifetime (createdAt + DEFAULT_SESSION_MAX_AGE_SEC).
+   * If the refresh token is expired or the absolute lifetime has elapsed, an error is thrown.
    */

320-330: Add tests for absolute lifetime and rounding behavior

Please add/adjust tests to cover:

  • Renewal fails when createdAt + DEFAULT_SESSION_MAX_AGE_SEC is in the past.
  • JWT maxAge aligns with the remaining absolute lifetime (rounding up from sub-second intervals).
  • Fallback behavior when createdAt is missing (if applicable to legacy data).

I can draft these unit/integration tests with table/fixtures for sessions and clock control (using fake timers). Want me to open a PR with the test suite?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 90fa45d and a2483ac.

📒 Files selected for processing (1)
  • controlplane/src/core/auth-utils.ts (2 hunks)
🔇 Additional comments (2)
controlplane/src/core/auth-utils.ts (2)

327-330: LGTM: refuse renewal once absolute lifetime has elapsed

The guard correctly enforces a fixed session lifetime relative to creation.


322-324: createdAt column enforces non-null with default now()
The sessions table schema defines createdAt as

timestamp('created_at', { withTimezone: true }).notNull().defaultNow()

so every new session row will always have a valid timestamp. The ?? Date.now() fallback only applies to very old rows that predate this column.

Copy link

@coderabbitai coderabbitai bot left a comment

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)
controlplane/src/core/auth-utils.ts (2)

324-324: Use Math.ceil for TTL to avoid sub-second early expiry flakiness

Using Math.floor can cause the JWT to expire up to ~1s before the cookie’s expires attribute, creating a brief mismatch. Ceil keeps them aligned (the cookie still enforces the absolute boundary).

-      const remainingSeconds = Math.max(0, Math.floor((expiresAtMs - Date.now()) / 1000));
+      const remainingSeconds = Math.max(0, Math.ceil((expiresAtMs - Date.now()) / 1000));

326-329: Clear the expired cookie before throwing to avoid client retry loops

When the absolute lifetime has elapsed, proactively expire the session cookie so clients don’t keep sending an already-dead cookie and hitting 401s repeatedly.

       if (remainingSeconds <= 0) {
-        // Absolute session lifetime has elapsed; do not renew.
+        // Absolute session lifetime has elapsed; do not renew.
+        // Proactively clear the cookie to avoid client retry loops with a stale session cookie.
+        this.clearCookie(res, this.opts.session.cookieName);
         throw new AuthenticationError(EnumStatusCode.ERROR_NOT_AUTHENTICATED, 'Session expired');
       }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a2483ac and 6dbec1d.

📒 Files selected for processing (1)
  • controlplane/src/core/auth-utils.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
controlplane/src/core/auth-utils.ts (2)
controlplane/src/core/crypto/jwt.ts (1)
  • DEFAULT_SESSION_MAX_AGE_SEC (7-7)
controlplane/src/core/errors/errors.ts (1)
  • AuthenticationError (19-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: build_push_image
  • GitHub Check: build_test
🔇 Additional comments (2)
controlplane/src/core/auth-utils.ts (2)

356-356: JWT TTL aligned with absolute session expiry — LGTM

Using remainingSeconds for maxAge keeps the encrypted cookie/JWT validity aligned with the absolute session end. Good.


320-325: Harden createdAt handling and clamp future‐skewed timestamps

Before trusting userSession.createdAt, make sure it’s always present and a real Date and then clamp it to “now” so clock skew can’t silently extend a session’s absolute lifetime:

   // The session expiration is relative to the creation time
+  // Ensure `createdAt` exists, is a Date, and not in the future.
+  const createdAtMs = userSession.createdAt?.getTime?.();
+  if (createdAtMs == null || Number.isNaN(createdAtMs)) {
+    throw new AuthenticationError(
+      EnumStatusCode.ERROR_NOT_AUTHENTICATED,
+      'Invalid session: missing or invalid creation timestamp',
+    );
+  }
+  const baseMs = Math.min(createdAtMs, Date.now());
   const expiresAtMs = baseMs + DEFAULT_SESSION_MAX_AGE_SEC * 1000;
   const sessionExpiresDate = new Date(expiresAtMs);
   const remainingSeconds = Math.max(0, Math.floor((expiresAtMs - Date.now()) / 1000));

Before merging, please verify that:

  • Your JWT‐payload type (UserSession) actually declares a createdAt: Date field (either directly or via JWTPayload).
  • When you call encrypt<UserSession>({ token: { … } }), you include createdAt: new Date() so every session token carries a valid timestamp.

Copy link
Contributor

@StarpTech StarpTech left a comment

Choose a reason for hiding this comment

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

LGTM

@wilsonrivera wilsonrivera merged commit fb585be into main Aug 21, 2025
9 checks passed
@wilsonrivera wilsonrivera deleted the wilson/eng-7923-session-should-expire-with-keycloaks-session branch August 21, 2025 20:57
@Noroth Noroth mentioned this pull request Sep 30, 2025
5 tasks
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.

3 participants