Skip to content

feat: dashboard auth subsystem — token hierarchy + session cookies (AD-2)#17

Merged
thebtf merged 6 commits into
mainfrom
feat/dashboard-auth
Mar 19, 2026
Merged

feat: dashboard auth subsystem — token hierarchy + session cookies (AD-2)#17
thebtf merged 6 commits into
mainfrom
feat/dashboard-auth

Conversation

@thebtf
Copy link
Copy Markdown
Owner

@thebtf thebtf commented Mar 18, 2026

Summary

Implements the token hierarchy auth system for the Engram Dashboard (Phase 1 of dashboard v1.0):

  • Migration 036: api_tokens table with UUID PK, bcrypt hash, prefix index, usage stats
  • TokenStore: CRUD + batch stats increment (5s flush)
  • Auth handlers: login (master token → httpOnly cookie), logout, me, token CRUD (create/list/revoke)
  • Middleware update: 3 auth methods — master token (header), client token (eng_* + bcrypt), HMAC-SHA256 session cookie
  • Token format: eng_{32-hex} — shown once at creation, bcrypt hash stored
  • Read-only scope: blocks POST/PUT/DELETE except semantic search endpoints
  • Buffered stats: async channel → batch DB update every 5s

Architecture Decisions

  • AD-2: Master admin token + generated client tokens + per-token stats
  • Cookie key derived from master token via SHA-256 (deterministic, no extra config)
  • Token lookup by prefix (8 chars) → bcrypt verify only on match

Test plan

  • POST /api/auth/login with master token → returns cookie
  • POST /api/auth/tokens → returns eng_* raw token (shown once)
  • Agent authenticates with client token → request_count incremented
  • Read-only token → POST /api/observations/archive returns 403
  • Read-only token → POST /api/context/search returns 200
  • go build ./cmd/worker/ compiles

Summary by CodeRabbit

Примечания к выпуску

  • Новые функции

    • Полная система управления API токенами с функциями создания, просмотра списка и отзыва токенов.
    • Реализована аутентификация на основе сессий для защиты веб-интерфейса.
    • Добавлено отслеживание и логирование статистики использования токенов в фоновом режиме.
  • Миграции базы данных

    • Добавлена новая таблица для хранения и управления API токенами.

thebtf added 6 commits March 19, 2026 02:07
Create api_tokens table for client token authentication with bcrypt
hash storage, prefix-based lookup index, and usage tracking columns.
GORM-based store with Create, List, FindByPrefix, Revoke,
IncrementStats, IncrementErrorCount, GetByID, and BatchIncrementStats
methods. APIToken model added to models.go.
Handlers for POST /api/auth/login (master token validation + HMAC cookie),
POST /api/auth/logout (cookie clear), GET /api/auth/me (status check),
GET/POST /api/auth/tokens (list/create), DELETE /api/auth/tokens/:id (revoke).
All handlers include swaggo annotations.
TokenAuth now supports three auth methods: master token (admin),
client API tokens (eng_* prefix with bcrypt verification and scope
enforcement), and HMAC-SHA256 signed session cookies (dashboard).
Read-only tokens are restricted to GET + whitelisted POST endpoints.
HMAC cookie key derived deterministically from master token via SHA-256.
Add public routes (login, logout) and authenticated routes (me, tokens
CRUD) to setupRoutes(). Create TokenStore in initializeAsync() and
wire it into TokenAuth middleware after DB initialization.
Background goroutine reads token IDs from a buffered channel and
flushes accumulated request counts to the database every 5 seconds,
reducing per-request UPDATE overhead for client token authentication.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 18, 2026

Walkthrough

Добавлена система управления API-токенами с поддержкой аутентификации на основе токенов и сессий. Включены миграция БД, модель данных, хранилище токенов, HTTP-обработчики и фоновый сборщик статистики использования токенов.

Changes

Cohort / File(s) Summary
Database Schema & Models
internal/db/gorm/migrations.go, internal/db/gorm/models.go
Добавлена миграция 036_api_tokens, которая создаёт таблицу api_tokens с полями для хеша, префикса, области действия и счётчиков; добавлен индекс на префикс для невозвращённых токенов. Определена модель GORM APIToken с методом TableName.
Token Storage Layer
internal/db/gorm/token_store.go
Реализовано хранилище TokenStore с методами для создания, перечисления, поиска по префиксу, отзыва, обновления статистики и пакетного увеличения счётчиков токенов.
Authentication Handlers
internal/worker/handlers_auth.go
Добавлены HTTP-обработчики для входа/выхода, проверки сессии, управления API-токенами (создание, перечисление, отзыв). Включает генерацию токенов с хешированием bcrypt, проверку HMAC-подписей и обработку ошибок уникальности.
Middleware & Service Integration
internal/worker/middleware.go, internal/worker/service.go
Расширена аутентификация в middleware для поддержки трёх методов (master-токен, client-токен, сессионная cookie). Добавлены валидация client-токенов, проверка отзыва и контроль области действия. Подключено хранилище токенов в Service с маршрутами API.
Token Statistics
internal/worker/token_stats.go
Добавлен фоновый сборщик статистики, который накапливает счётчики использования токенов из канала и периодически (каждые 5 сек) сохраняет их в БД.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Middleware as Middleware
    participant TokenStore as TokenStore
    participant Database as Database
    participant Handler as Handler

    Client->>Middleware: POST /api/auth/tokens (create token)
    Middleware->>Handler: authenticateClientToken (validate master token)
    Handler->>Handler: Generate random token + bcrypt hash
    Handler->>TokenStore: Create(name, hash, prefix, scope)
    TokenStore->>Database: INSERT INTO api_tokens
    Database-->>TokenStore: token created
    TokenStore-->>Handler: APIToken object
    Handler-->>Client: 200 OK (id, name, raw_token, scope)

    Client->>Middleware: GET /api/auth/tokens (list tokens)
    Middleware->>Handler: validateMasterToken
    Handler->>TokenStore: List()
    TokenStore->>Database: SELECT * FROM api_tokens
    Database-->>TokenStore: tokens list
    TokenStore-->>Handler: []APIToken (without hashes)
    Handler-->>Client: 200 OK (token list)

    Client->>Middleware: Request with client token header
    Middleware->>Middleware: Extract token prefix
    Middleware->>TokenStore: FindByPrefix(prefix)
    TokenStore->>Database: SELECT FROM api_tokens WHERE token_prefix = ?
    Database-->>TokenStore: token found
    Middleware->>Middleware: bcrypt verify token hash
    Middleware->>Middleware: Check token not revoked
    Middleware->>Middleware: Enforce read-only scope if needed
    Middleware->>Handler: Set context with token scope
    Handler-->>Client: Request processed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

ai-review:passed

Poem

🐰 Токены хешируем с заботой,
В БД хранятся без забот,
Статистика фоном поёт,
Безопасность — вот мой черёд!
API блистает в добрый час, 🔐✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: implementing a dashboard authentication subsystem with token hierarchy and session cookies, directly aligned with the substantial changes across migrations, models, handlers, and middleware.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dashboard-auth
📝 Coding Plan
  • Generate coding plan for human review comments

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.3)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


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

@gemini-code-assist
Copy link
Copy Markdown

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 significantly enhances the dashboard's security and access control by implementing a robust token hierarchy authentication system. It provides granular control over API access through client tokens with defined scopes, alongside a secure session management mechanism for administrative users. The changes lay the groundwork for a more scalable and manageable authentication infrastructure, improving both security and performance by optimizing database interactions for token usage tracking.

Highlights

  • New Authentication Subsystem: Introduced a comprehensive token hierarchy authentication system for the dashboard, supporting master tokens, client API tokens, and HMAC-signed session cookies.
  • API Token Management: Added a new api_tokens database table and a TokenStore for CRUD operations on client API tokens, including features like unique names, bcrypt hashing, prefix-based lookup, and usage statistics.
  • Dashboard Authentication Handlers: Implemented new HTTP handlers for user login (via master token to obtain a session cookie), logout, checking authentication status (/me), and managing client API tokens (list, create, revoke).
  • Enhanced Authentication Middleware: Updated the authentication middleware to support three distinct authentication methods: master token, client API tokens (with eng_ prefix and bcrypt verification), and HMAC-SHA256 session cookies. It also enforces read-only scopes for client tokens and includes an asynchronous mechanism for updating token usage statistics.
  • Asynchronous Token Stats: Developed a buffered channel and a background flusher to batch and asynchronously update client API token usage statistics in the database every 5 seconds, reducing per-request database overhead.
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.

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.

Copy link
Copy Markdown

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

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 introduces a comprehensive token-based authentication system, including a token hierarchy with master and client tokens, and session management using HMAC-signed cookies. The implementation is solid, with good use of security best practices like constant-time comparisons for tokens, bcrypt for hashing, and secure cookie attributes. The addition of a buffered stats flusher is a great performance optimization. I've identified a few areas for improvement related to error handling robustness, code consistency, and efficiency.

Comment on lines +272 to +277
// Extract prefix: chars 4-12 (first 8 hex chars after "eng_")
if len(rawToken) < 12 {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return true
}
prefix := rawToken[4:12]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The token prefix extraction logic in authenticateClientToken uses magic numbers (e.g., len(rawToken) < 12, rawToken[4:12]). This makes the code harder to maintain and understand. The constants tokenRawPrefix and tokenPrefixLen are already defined in the worker package (in handlers_auth.go) and should be used here for consistency and clarity.

Suggested change
// Extract prefix: chars 4-12 (first 8 hex chars after "eng_")
if len(rawToken) < 12 {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return true
}
prefix := rawToken[4:12]
// Extract prefix: first 8 hex chars after "eng_"
if len(rawToken) < len(tokenRawPrefix)+tokenPrefixLen {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return true
}
prefix := rawToken[len(tokenRawPrefix) : len(tokenRawPrefix)+tokenPrefixLen]

Comment on lines +318 to +322
if err := tokenStore.Revoke(r.Context(), id); err != nil {
log.Error().Err(err).Str("token_id", id).Msg("auth: failed to revoke token")
http.Error(w, "not found", http.StatusNotFound)
return
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The error handling for tokenStore.Revoke is not specific enough. It treats all errors, including potential database connection issues, as a "not found" error and returns a 404 status. This can mask underlying problems. It's better to specifically check for gorm.ErrRecordNotFound to return a 404, and return a 500 Internal Server Error for all other errors.

You'll need to add import ("errors"; "gorm.io/gorm") to this file.

Suggested change
if err := tokenStore.Revoke(r.Context(), id); err != nil {
log.Error().Err(err).Str("token_id", id).Msg("auth: failed to revoke token")
http.Error(w, "not found", http.StatusNotFound)
return
}
if err := tokenStore.Revoke(r.Context(), id); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
http.Error(w, "token not found", http.StatusNotFound)
return
}
log.Error().Err(err).Str("token_id", id).Msg("auth: failed to revoke token")
http.Error(w, "internal error", http.StatusInternalServerError)
return
}

Comment on lines +348 to +370
// isDuplicateKeyError checks if the error is a unique constraint violation.
func isDuplicateKeyError(err error) bool {
if err == nil {
return false
}
msg := err.Error()
// PostgreSQL unique violation error code 23505
return containsDuplicateKey(msg)
}

// containsDuplicateKey checks error message for duplicate key indicators.
func containsDuplicateKey(msg string) bool {
for _, s := range []string{"duplicate key", "23505", "UNIQUE constraint"} {
if len(msg) >= len(s) {
for i := 0; i <= len(msg)-len(s); i++ {
if msg[i:i+len(s)] == s {
return true
}
}
}
}
return false
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The current implementation for detecting duplicate key errors relies on string matching, which is fragile and can break with database or driver updates. A more robust approach is to type-assert the error to *pq.Error and check for the specific SQLSTATE code 23505. This makes the error handling independent of the error message string. Additionally, the containsDuplicateKey function is an inefficient, manual implementation of strings.Contains.

This suggestion replaces both isDuplicateKeyError and containsDuplicateKey with a single, more robust function. You'll need to add import ("errors"; "strings"; "github.com/lib/pq") to this file.

// isDuplicateKeyError checks if the error is a unique constraint violation.
func isDuplicateKeyError(err error) bool {
	if err == nil {
		return false
	}

	// Check for PostgreSQL unique violation error code 23505
	var pqErr *pq.Error
	if errors.As(err, &pqErr) {
		return pqErr.Code == "23505"
	}

	// Fallback for other DB drivers or error types
	msg := err.Error()
	return strings.Contains(msg, "UNIQUE constraint") || strings.Contains(msg, "duplicate key")
}

Comment on lines +48 to +50
for k := range pending {
delete(pending, k)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The pending map is cleared by iterating over all its keys. A more idiomatic and efficient way to clear a map in Go is to create a new one, which avoids the loop and lets the garbage collector handle the old map's memory.

				pending = make(map[string]int)

Copy link
Copy Markdown

@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: 4

🧹 Nitpick comments (1)
internal/worker/handlers_auth.go (1)

358-370: Рекомендуется использовать strings.Contains вместо ручного поиска подстроки.

Текущая реализация вручную итерирует по строке для поиска подстроки. Стандартная библиотека Go предоставляет strings.Contains для этой цели.

♻️ Предлагаемый рефакторинг
+import "strings"
+
 // containsDuplicateKey checks error message for duplicate key indicators.
 func containsDuplicateKey(msg string) bool {
-	for _, s := range []string{"duplicate key", "23505", "UNIQUE constraint"} {
-		if len(msg) >= len(s) {
-			for i := 0; i <= len(msg)-len(s); i++ {
-				if msg[i:i+len(s)] == s {
-					return true
-				}
-			}
-		}
-	}
-	return false
+	return strings.Contains(msg, "duplicate key") ||
+		strings.Contains(msg, "23505") ||
+		strings.Contains(msg, "UNIQUE constraint")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/worker/handlers_auth.go` around lines 358 - 370, The
containsDuplicateKey function manually searches substrings which is error-prone
and inefficient; replace the manual loop with calls to strings.Contains for each
indicator string. Import the "strings" package if not already present, then in
containsDuplicateKey iterate the slice {"duplicate key","23505","UNIQUE
constraint"} and return true when strings.Contains(msg, s) is true, otherwise
return false. Ensure function name containsDuplicateKey remains unchanged and no
other behavior is modified.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/db/gorm/token_store.go`:
- Around line 63-77: In TokenStore.Revoke, the code currently checks
result.RowsAffected before result.Error which can hide real DB errors; change
the logic to first inspect result.Error and return it if non-nil, then check
result.RowsAffected == 0 and return gorm.ErrRecordNotFound; keep the same update
payload (APIToken fields "revoked" and "revoked_at") and context usage
(s.db.WithContext(ctx)) but swap the error/rows-affected checks so the real DB
error from result.Error is propagated.

In `@internal/worker/handlers_auth.go`:
- Around line 118-125: The logout cookie currently cleared via the
http.SetCookie call for sessionCookieName is missing the Secure flag; update
that http.SetCookie invocation (the block that sets Name: sessionCookieName,
Value: "" etc.) to include Secure: true so the cookie is only sent over HTTPS
(matching the login cookie behavior).
- Around line 95-102: The session cookie is being set without the Secure flag
which allows transmission over plain HTTP; update the http.SetCookie call that
constructs the cookie (the block using Name: sessionCookieName, Value:
cookieValue, Path: "/", MaxAge: sessionMaxAge, HttpOnly: true, SameSite:
http.SameSiteStrictMode) to include a Secure field whose value is derived from
runtime configuration or an environment variable (e.g., isProd or
cfg.IsProduction) so Secure is true in production and false in local dev; ensure
the chosen config symbol is checked where the handler runs and used to set
Secure accordingly.

In `@internal/worker/middleware.go`:
- Around line 236-249: The extra unreachable return after calling
authenticateClientToken should be removed: in the client-token branch (where
providedToken is checked for the "eng_" prefix and store != nil) call
ta.authenticateClientToken(w, r, next, providedToken, store) and then
immediately return only once (or simply rely on authenticateClientToken to
handle responses and stop execution) — delete the redundant second return so the
block contains a single return after the authenticateClientToken invocation and
no unreachable statement remains.

---

Nitpick comments:
In `@internal/worker/handlers_auth.go`:
- Around line 358-370: The containsDuplicateKey function manually searches
substrings which is error-prone and inefficient; replace the manual loop with
calls to strings.Contains for each indicator string. Import the "strings"
package if not already present, then in containsDuplicateKey iterate the slice
{"duplicate key","23505","UNIQUE constraint"} and return true when
strings.Contains(msg, s) is true, otherwise return false. Ensure function name
containsDuplicateKey remains unchanged and no other behavior is modified.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b5fdff9a-7ea5-4586-99b3-a5131a1ed7c1

📥 Commits

Reviewing files that changed from the base of the PR and between a8a3fe9 and 37fb2b4.

📒 Files selected for processing (7)
  • internal/db/gorm/migrations.go
  • internal/db/gorm/models.go
  • internal/db/gorm/token_store.go
  • internal/worker/handlers_auth.go
  • internal/worker/middleware.go
  • internal/worker/service.go
  • internal/worker/token_stats.go

Comment on lines +63 to +77
// Revoke marks a token as revoked.
func (s *TokenStore) Revoke(ctx context.Context, id string) error {
now := time.Now()
result := s.db.WithContext(ctx).
Model(&APIToken{}).
Where("id = ?", id).
Updates(map[string]interface{}{
"revoked": true,
"revoked_at": &now,
})
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return result.Error
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Потенциальная проблема: проверка ошибки после RowsAffected.

Если result.Error не nil, метод вернёт gorm.ErrRecordNotFound, скрывая реальную ошибку БД. Следует сначала проверить result.Error.

🐛 Предлагаемое исправление
 func (s *TokenStore) Revoke(ctx context.Context, id string) error {
 	now := time.Now()
 	result := s.db.WithContext(ctx).
 		Model(&APIToken{}).
 		Where("id = ?", id).
 		Updates(map[string]interface{}{
 			"revoked":    true,
 			"revoked_at": &now,
 		})
+	if result.Error != nil {
+		return result.Error
+	}
 	if result.RowsAffected == 0 {
 		return gorm.ErrRecordNotFound
 	}
-	return result.Error
+	return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Revoke marks a token as revoked.
func (s *TokenStore) Revoke(ctx context.Context, id string) error {
now := time.Now()
result := s.db.WithContext(ctx).
Model(&APIToken{}).
Where("id = ?", id).
Updates(map[string]interface{}{
"revoked": true,
"revoked_at": &now,
})
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return result.Error
}
// Revoke marks a token as revoked.
func (s *TokenStore) Revoke(ctx context.Context, id string) error {
now := time.Now()
result := s.db.WithContext(ctx).
Model(&APIToken{}).
Where("id = ?", id).
Updates(map[string]interface{}{
"revoked": true,
"revoked_at": &now,
})
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/db/gorm/token_store.go` around lines 63 - 77, In TokenStore.Revoke,
the code currently checks result.RowsAffected before result.Error which can hide
real DB errors; change the logic to first inspect result.Error and return it if
non-nil, then check result.RowsAffected == 0 and return gorm.ErrRecordNotFound;
keep the same update payload (APIToken fields "revoked" and "revoked_at") and
context usage (s.db.WithContext(ctx)) but swap the error/rows-affected checks so
the real DB error from result.Error is propagated.

Comment on lines +95 to +102
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: cookieValue,
Path: "/",
MaxAge: sessionMaxAge,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Отсутствует флаг Secure для production-окружения.

Сессионная cookie не имеет флага Secure: true, что позволит передачу cookie через HTTP. В production это риск перехвата сессии.

🔒 Предлагаемое исправление
 http.SetCookie(w, &http.Cookie{
 	Name:     sessionCookieName,
 	Value:    cookieValue,
 	Path:     "/",
 	MaxAge:   sessionMaxAge,
 	HttpOnly: true,
+	Secure:   true, // Set to false only for local development
 	SameSite: http.SameSiteStrictMode,
 })

Рассмотрите возможность определения флага Secure на основе конфигурации или переменной окружения для различения dev/prod.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: cookieValue,
Path: "/",
MaxAge: sessionMaxAge,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
})
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: cookieValue,
Path: "/",
MaxAge: sessionMaxAge,
HttpOnly: true,
Secure: true, // Set to false only for local development
SameSite: http.SameSiteStrictMode,
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/worker/handlers_auth.go` around lines 95 - 102, The session cookie
is being set without the Secure flag which allows transmission over plain HTTP;
update the http.SetCookie call that constructs the cookie (the block using Name:
sessionCookieName, Value: cookieValue, Path: "/", MaxAge: sessionMaxAge,
HttpOnly: true, SameSite: http.SameSiteStrictMode) to include a Secure field
whose value is derived from runtime configuration or an environment variable
(e.g., isProd or cfg.IsProduction) so Secure is true in production and false in
local dev; ensure the chosen config symbol is checked where the handler runs and
used to set Secure accordingly.

Comment on lines +118 to +125
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Отсутствует флаг Secure в cookie logout.

Аналогично cookie при login, здесь также следует добавить Secure: true.

🔒 Предлагаемое исправление
 http.SetCookie(w, &http.Cookie{
 	Name:     sessionCookieName,
 	Value:    "",
 	Path:     "/",
 	MaxAge:   -1,
 	HttpOnly: true,
+	Secure:   true,
 	SameSite: http.SameSiteStrictMode,
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
})
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/worker/handlers_auth.go` around lines 118 - 125, The logout cookie
currently cleared via the http.SetCookie call for sessionCookieName is missing
the Secure flag; update that http.SetCookie invocation (the block that sets
Name: sessionCookieName, Value: "" etc.) to include Secure: true so the cookie
is only sent over HTTPS (matching the login cookie behavior).

Comment on lines +236 to 249
// 1b. Check if it's a client token (eng_* prefix)
if strings.HasPrefix(providedToken, "eng_") && store != nil {
if ta.authenticateClientToken(w, r, next, providedToken, store) {
return
}
// authenticateClientToken wrote the error response if it failed
return
}

// Token provided but doesn't match anything
log.Warn().Str("path", r.URL.Path).Str("remote_addr", r.RemoteAddr).Msg("auth: rejected request with invalid token")
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Избыточный return после вызова authenticateClientToken.

Строки 241-243 содержат избыточный return. Метод authenticateClientToken всегда возвращает true и сам обрабатывает как успешные, так и неуспешные случаи. Второй return на строке 243 недостижим.

🧹 Предлагаемое исправление
 // 1b. Check if it's a client token (eng_* prefix)
 if strings.HasPrefix(providedToken, "eng_") && store != nil {
-	if ta.authenticateClientToken(w, r, next, providedToken, store) {
-		return
-	}
-	// authenticateClientToken wrote the error response if it failed
+	ta.authenticateClientToken(w, r, next, providedToken, store)
 	return
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/worker/middleware.go` around lines 236 - 249, The extra unreachable
return after calling authenticateClientToken should be removed: in the
client-token branch (where providedToken is checked for the "eng_" prefix and
store != nil) call ta.authenticateClientToken(w, r, next, providedToken, store)
and then immediately return only once (or simply rely on authenticateClientToken
to handle responses and stop execution) — delete the redundant second return so
the block contains a single return after the authenticateClientToken invocation
and no unreachable statement remains.

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.

1 participant