Skip to content

fix: restore admin token auth in opaque session mode#884

Merged
ding113 merged 1 commit into
devfrom
fix/admin-token-opaque-fallback
Mar 8, 2026
Merged

fix: restore admin token auth in opaque session mode#884
ding113 merged 1 commit into
devfrom
fix/admin-token-opaque-fallback

Conversation

@ding113

@ding113 ding113 commented Mar 8, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a regression in v0.6 where SESSION_TOKEN_MODE=opaque (the default) broke programmatic API access using raw ADMIN_TOKEN. Adds secure fallback validation to restore backward compatibility for /api/* endpoints while maintaining the security posture of opaque session mode.

Problem

In v0.6, the session token mode defaults to opaque as part of the security overhaul (#803). However, this broke a critical use case:

  • Programmatic API calls using ADMIN_TOKEN via cookie or Bearer header were rejected
  • validateAuthToken() only accepted opaque session IDs (sid_*) in opaque mode
  • The raw ADMIN_TOKEN validation path was never reached when mode was opaque

This prevented server-side scripts and external integrations from authenticating with the admin token when the application was in opaque session mode.

Solution

Added a constantTimeEqual check for the admin token in the opaque-mode path of validateAuthToken() before returning null:

// Opaque mode: allow raw ADMIN_TOKEN for backward-compatible programmatic API access.
// Safe because admin token is a server-side env secret, not a user-issued DB key.
const adminToken = config.auth.adminToken;
if (adminToken && constantTimeEqual(token, adminToken)) {
  return validateKey(token, options);
}

Security considerations:

  • Admin token is a server-side environment secret, not a user-issued database key
  • Regular (non-admin) API keys remain correctly rejected in opaque mode
  • Security posture is not degraded - the check uses constant-time comparison

Changes

File Change
src/lib/auth.ts Added admin token fallback check in opaque mode path
tests/unit/auth/admin-token-opaque-fallback.test.ts New comprehensive test suite (6 test cases)

Related

Testing

New Unit Tests

  • tests/unit/auth/admin-token-opaque-fallback.test.ts (6 cases)
    • Opaque mode + admin cookie -> succeeds
    • Opaque mode + non-admin cookie -> fails (security)
    • Opaque mode + admin Bearer header -> succeeds
    • Opaque mode + valid opaque session -> succeeds (no regression)
    • Legacy mode -> unchanged behavior
    • Opaque mode + no admin token configured -> fails

Regression Tests

  • tests/security/auth-dual-read.test.ts (6/6 pass)
  • tests/unit/auth/opaque-admin-session.test.ts (3/3 pass)

Checklist

  • Code follows project conventions
  • Self-review completed
  • Tests pass locally
  • Security impact assessed

Original description preserved below

Summary

  • In v0.6, SESSION_TOKEN_MODE defaults to opaque. This broke programmatic API calls using raw ADMIN_TOKEN via cookie or Bearer header, since validateAuthToken() only accepted opaque session IDs (sid_*) and never fell back to key validation.
  • Added a constantTimeEqual check for admin token in the opaque-mode path of validateAuthToken(), restoring backward-compatible access for all /api/* endpoints.
  • Regular (non-admin) API keys remain correctly rejected in opaque mode -- security posture is not degraded.

Test plan

  • New test: tests/unit/auth/admin-token-opaque-fallback.test.ts (6 cases)
    • opaque + admin cookie -> succeeds
    • opaque + non-admin cookie -> fails (security)
    • opaque + admin Bearer header -> succeeds
    • opaque + valid opaque session -> succeeds (no regression)
    • legacy mode -> unchanged behavior
    • opaque + no admin token configured -> fails
  • Regression: tests/security/auth-dual-read.test.ts (6/6 pass)
  • Regression: tests/unit/auth/opaque-admin-session.test.ts (3/3 pass)

Greptile Summary

This PR restores backward-compatible ADMIN_TOKEN access in opaque session mode by adding a constantTimeEqual fallback inside validateAuthToken() that executes when the opaque session store returns no match. Regular (non-admin) raw API keys remain correctly rejected in opaque mode, preserving security posture.

Implementation: Adds a 7-line admin-token fallback block at the bottom of validateAuthToken() (after the opaque session store lookup, after the legacy/dual mode branch) that delegates to the existing validateKey() function, which constructs the virtual admin user. Test coverage is comprehensive (6 cases covering cookie, Bearer header, opaque session, legacy mode, and unconfigured token paths).

Security concern: The raw ADMIN_TOKEN value is passed to sessionStore.read(token) (line 272) before the constantTimeEqual check fires (line 297). In production environments with Redis command logging or observability sidecars, this exposes the plaintext token in logs. Moving the admin-token check to run before the session-store call would eliminate this exposure and save a Redis round-trip for every programmatic admin request.

Confidence Score: 2/5

  • The admin-token fallback logic itself is correct and preserves security, but the raw token is sent to Redis before validation, risking exposure in command logs—resolve before production deployment.
  • The functional implementation of the admin-token fallback is correct and test coverage is thorough. However, there is a concrete operational security concern: in opaque mode, the raw ADMIN_TOKEN is forwarded to Redis before the equality check, which can expose the secret in Redis command logs if logging is enabled (a common production practice). This is not a functional bug but a meaningful security issue that should be addressed before merging to production. The fix is straightforward: validate the admin token before calling sessionStore.read().
  • src/lib/auth.ts — specifically the order of operations in validateAuthToken() around the session-store lookup (lines 269–301). The admin-token check should execute before the Redis call.

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant VA as validateAuthToken()
    participant SS as SessionStore (Redis)
    participant VK as validateKey()

    C->>VA: token (cookie or Bearer)
    alt mode == legacy
        VA->>VK: validateKey(token)
        VK-->>VA: AuthSession | null
    else mode == dual
        VA->>SS: read(token)
        SS-->>VA: session | null
        alt session found & valid
            VA->>VK: validateKey via convertToAuthSession
            VK-->>VA: AuthSession
        else session not found
            VA->>VK: validateKey(token)
            VK-->>VA: AuthSession | null
        end
    else mode == opaque (current PR)
        VA->>SS: read(token) ⚠️ raw ADMIN_TOKEN sent here
        SS-->>VA: session | null
        alt session found & valid
            VA->>VK: convertToAuthSession → validateKey
            VK-->>VA: AuthSession
        else session not found
            VA->>VA: constantTimeEqual(token, adminToken)?
            alt token == ADMIN_TOKEN
                VA->>VK: validateKey(token)
                VK-->>VA: admin AuthSession (id=-1)
            else not admin token
                VA-->>C: null (rejected)
            end
        end
    end
    VA-->>C: AuthSession | null
Loading

Comments Outside Diff (1)

  1. src/lib/auth.ts, line 269-301 (link)

    Raw admin token forwarded to Redis before equality check

    In opaque mode, the raw token string is passed to sessionStore.read(token) on line 272 before the constantTimeEqual guard on line 297. This means if a request includes a raw ADMIN_TOKEN in the cookie or Bearer header, it will be sent to Redis as a lookup key. If Redis command logging or any monitoring sidecar is enabled in production, the plaintext ADMIN_TOKEN will appear in those logs.

    Recommend moving the admin-token check to execute before the session-store lookup (guarded with mode === "opaque"). This would:

    1. Prevent the token from being sent to Redis as a key, eliminating exposure in Redis logs
    2. Preserve existing behavior for legacy and dual modes
    3. Save one Redis round-trip per admin-token request

Last reviewed commit: 171fa9c

In opaque mode, validateAuthToken() rejected raw ADMIN_TOKEN passed via
cookie or Authorization header, breaking programmatic API access that
worked in legacy/dual mode. Add a constantTimeEqual check for the admin
token before returning null, so server-side env secret still authenticates
while regular API keys remain correctly rejected.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical authentication regression where the ADMIN_TOKEN could not be used for programmatic API access when the system was configured in opaque session mode. By integrating a direct validation check for the ADMIN_TOKEN within the opaque session handling logic, the change restores essential administrative functionality without compromising the security design for other API keys. This ensures that systems relying on the ADMIN_TOKEN for automated tasks can continue to operate seamlessly after the v0.6 update.

Highlights

  • Admin Token Authentication Fix: Resolved an issue where programmatic API calls using the raw ADMIN_TOKEN via cookie or Bearer header were broken in opaque session mode, which became the default in v0.6.
  • Backward Compatibility Restoration: Implemented a constantTimeEqual check for the admin token within the opaque-mode path of validateAuthToken(), restoring backward-compatible access for all /api/* endpoints.
  • Security Posture Maintained: Ensured that regular (non-admin) API keys continue to be correctly rejected in opaque mode, preventing any degradation of the security posture.
  • Comprehensive Test Coverage: Introduced a new unit test file (admin-token-opaque-fallback.test.ts) with 6 test cases to thoroughly validate the fix, covering various scenarios including success with admin token, failure with non-admin keys, and behavior in legacy mode.
Changelog
  • src/lib/auth.ts
    • Added a constantTimeEqual check to validateAuthToken to allow ADMIN_TOKEN validation in opaque session mode.
  • tests/unit/auth/admin-token-opaque-fallback.test.ts
    • Added a new test file to verify the ADMIN_TOKEN fallback mechanism in opaque session mode.
    • Included test cases for admin token via cookie, admin token via Bearer header, non-admin API key rejection, valid opaque session, legacy mode behavior, and unconfigured admin token.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai

coderabbitai Bot commented Mar 8, 2026

Copy link
Copy Markdown
📝 Walkthrough

总体概述

在不透明模式下的身份验证中添加了后向兼容的管理员令牌回退机制。当配置的管理员令牌存在且与提供的令牌相匹配时,系统调用密钥验证逻辑,启用了编程式访问。同时补充了相应的单元测试覆盖。

变更

组织单元 / 文件 摘要
身份验证逻辑修改
src/lib/auth.ts
在 validateAuthToken 的不透明模式下添加了管理员令牌回退路径。当配置的 adminToken 存在且通过常数时间比较与提供的令牌相等时,调用 validateKey(),允许在不透明模式下将原始 ADMIN_TOKEN 作为有效凭证处理。
测试覆盖
tests/unit/auth/admin-token-opaque-fallback.test.ts
新增全面的单元测试套件,覆盖不透明模式和遗留模式下的管理员令牌回退行为,包括 Cookie/Bearer 令牌处理、会话读取、密钥验证和配置缺失等场景。

代码审查工作量评估

🎯 3 (中等) | ⏱️ ~20 分钟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰准确地总结了主要变更:在不透明会话模式下恢复管理员令牌认证功能。
Description check ✅ Passed PR描述详细阐述了v0.6中opaque模式破坏ADMIN_TOKEN访问的问题、实现的解决方案、安全考虑以及全面的测试覆盖。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/admin-token-opaque-fallback

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.

@github-actions github-actions Bot added bug Something isn't working area:session labels Mar 8, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request correctly restores admin token authentication when using the opaque session mode, which is an important backward-compatibility fix. The change in src/lib/auth.ts is direct and addresses the issue by adding a fallback check for the admin token. The accompanying new test file, tests/unit/auth/admin-token-opaque-fallback.test.ts, is thorough and provides excellent coverage for the fix, including success cases, security-related failure cases, and regressions for other modes. I've added a couple of suggestions to improve the maintainability of the new test file by reducing code duplication.

Comment on lines +18 to +20
const mockConfig = vi.hoisted(() => ({
auth: { adminToken: "test-admin-secret-token-12345" },
}));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To improve maintainability and avoid duplicating the magic string for the admin token, it's better to define ADMIN_TOKEN as a constant at the top of the module. This constant can then be used both here in the hoisted mock and within the test suite. This prevents potential inconsistencies if the token value needs to be updated in the future.

Suggested change
const mockConfig = vi.hoisted(() => ({
auth: { adminToken: "test-admin-secret-token-12345" },
}));
const ADMIN_TOKEN = "test-admin-secret-token-12345";
const mockConfig = vi.hoisted(() => ({
auth: { adminToken: ADMIN_TOKEN },
}));

}

describe("admin token opaque-mode fallback", () => {
const ADMIN_TOKEN = "test-admin-secret-token-12345";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

With ADMIN_TOKEN defined at the module level as suggested previously, this local declaration becomes redundant and should be removed to avoid shadowing and confusion.

@github-actions github-actions Bot added the size/M Medium PR (< 500 lines) label Mar 8, 2026
@github-actions

github-actions Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@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.

🧹 Nitpick comments (2)
tests/unit/auth/admin-token-opaque-fallback.test.ts (2)

238-246: Mock 值与生产环境行为不一致。

测试将 mockConfig.auth.adminToken 设置为空字符串 "",但根据 env.schema.ts 的预处理逻辑,空字符串会被转换为 undefined。建议使用 undefined 以更准确地反映生产环境行为:

♻️ 建议修改
  it("opaque mode + admin token not configured -> auth fails for raw token", async () => {
-   mockConfig.auth.adminToken = "";
+   mockConfig.auth.adminToken = undefined;
    setAuthCookie("some-random-token");

    const { getSession } = await import("@/lib/auth");
    const session = await getSession();

    expect(session).toBeNull();
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/auth/admin-token-opaque-fallback.test.ts` around lines 238 - 246,
The test sets mockConfig.auth.adminToken = "" which doesn't match production
preprocessing that converts empty strings to undefined; update the test to set
mockConfig.auth.adminToken = undefined (in the "opaque mode + admin token not
configured -> auth fails for raw token" test) so behavior matches env.schema
preprocessing, keeping the calls to setAuthCookie("some-random-token") and the
subsequent import and call to getSession() unchanged.

18-20: Mock 类型需支持 undefined

若要在测试中将 adminToken 设置为 undefined,需要更新 mock 定义的类型:

♻️ 建议修改
 const mockConfig = vi.hoisted(() => ({
-  auth: { adminToken: "test-admin-secret-token-12345" },
+  auth: { adminToken: "test-admin-secret-token-12345" as string | undefined },
 }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/auth/admin-token-opaque-fallback.test.ts` around lines 18 - 20,
The current hoisted mock `mockConfig` defines `auth.adminToken` as a string
only, which prevents tests from setting it to undefined; update the mock's
type/signature so `adminToken` can be string | undefined (for example by
annotating `vi.hoisted` with a type where `auth: { adminToken?: string |
undefined }` or `adminToken: string | undefined`) and adjust the returned object
to allow `adminToken` to be undefined when needed so tests that set `adminToken
= undefined` compile and run.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/unit/auth/admin-token-opaque-fallback.test.ts`:
- Around line 238-246: The test sets mockConfig.auth.adminToken = "" which
doesn't match production preprocessing that converts empty strings to undefined;
update the test to set mockConfig.auth.adminToken = undefined (in the "opaque
mode + admin token not configured -> auth fails for raw token" test) so behavior
matches env.schema preprocessing, keeping the calls to
setAuthCookie("some-random-token") and the subsequent import and call to
getSession() unchanged.
- Around line 18-20: The current hoisted mock `mockConfig` defines
`auth.adminToken` as a string only, which prevents tests from setting it to
undefined; update the mock's type/signature so `adminToken` can be string |
undefined (for example by annotating `vi.hoisted` with a type where `auth: {
adminToken?: string | undefined }` or `adminToken: string | undefined`) and
adjust the returned object to allow `adminToken` to be undefined when needed so
tests that set `adminToken = undefined` compile and run.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c9ca0138-3aaa-4308-9b3b-c5306bc6aa5b

📥 Commits

Reviewing files that changed from the base of the PR and between 565f2e6 and 171fa9c.

📒 Files selected for processing (2)
  • src/lib/auth.ts
  • tests/unit/auth/admin-token-opaque-fallback.test.ts

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review Summary

No significant issues identified in this PR.

PR Size: M

  • Lines changed: 254
  • Files changed: 2

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Automated review by Codex AI

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed PR #884 (“fix: restore admin token auth in opaque session mode”).

  • Applied label: size/M (254 lines changed, 2 files).
  • Posted PR review summary via gh pr review (no diff-scoped issues met the >=80 confidence reporting threshold, so no inline review comments were added).

@ding113
ding113 merged commit d74d4b7 into dev Mar 8, 2026
21 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Mar 8, 2026
@github-actions github-actions Bot mentioned this pull request Mar 10, 2026
2 tasks
@ding113
ding113 deleted the fix/admin-token-opaque-fallback branch May 13, 2026 06:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:session bug Something isn't working size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant