Skip to content

Implement Token-Based Authentication System#75

Merged
Furisto merged 10 commits into
mainfrom
auth-api
Jan 3, 2026
Merged

Implement Token-Based Authentication System#75
Furisto merged 10 commits into
mainfrom
auth-api

Conversation

@Furisto
Copy link
Copy Markdown
Owner

@Furisto Furisto commented Jan 1, 2026

Overview

This PR implements a comprehensive token-based authentication system for Construct, enabling secure remote daemon access while maintaining backward compatibility with local Unix socket connections.

Architecture

Transport-Based Trust Model

Authentication is determined by connection transport:

  • Unix Socket: Implicit admin trust via OS filesystem permissions - no token required
  • TCP: Bearer token authentication with database validation and expiration checks

Key Components

Token Provider

  • Secure token generation using crypto/rand (256-bit entropy)
  • SHA-256 hashing for storage (plaintext tokens never persisted)
  • Setup code generation for secure token distribution (8-character codes, 20min expiry)
  • In-memory setup code storage with single-use consumption

Auth Interceptor

  • ConnectRPC middleware enforcing authentication on all endpoints
  • Exempts ExchangeSetupCode for unauthenticated bootstrap
  • Populates Identity context with subject, auth method, and privileges
  • Validates token expiry and database presence

AuthService RPCs

  • CreateToken: Generate new API tokens (admin only)
  • CreateSetupCode: Generate short-lived exchange codes (admin only)
  • ListTokens: Query tokens with filters (admin only)
  • RevokeToken: Delete tokens by ID (admin only)
  • ExchangeSetupCode: Bootstrap authentication without credentials (unauthenticated)

Database Schema

New Token entity with Ent:

  • id: UUID primary key
  • name: Unique human-readable identifier
  • type: Enum (api_token | setup_code)
  • token_hash: SHA-256 hash for validation
  • description: Optional metadata
  • expires_at: Expiration timestamp
  • created_at / updated_at: Automatic timestamps

Indexes on name and token_hash for efficient lookups.

Security Features

✅ Cryptographically secure token generation (crypto/rand)
✅ Only SHA-256 hashes stored in database
✅ Plaintext tokens shown once at creation
✅ Configurable token expiration (default 90 days, max 365 days)
✅ Setup codes expire quickly (default 20 minutes, max 72 hours)
✅ Single-use setup codes (deleted after consumption)
✅ Case-insensitive setup codes for usability
✅ Thread-safe setup code storage with mutex
✅ Admin-only token management operations

Setup Code Flow

Designed for secure token distribution without exposing tokens:

  1. Admin runs construct daemon token create --setup-code laptop via Unix socket
  2. Daemon generates short code like ABCD-1234, stores in memory (20min TTL)
  3. Admin shares code out-of-band (verbally, Slack, etc.)
  4. User runs construct context add prod --endpoint https://... --setup-code ABCD-1234
  5. Client calls ExchangeSetupCode RPC (unauthenticated)
  6. Daemon validates code, generates real token, deletes code
  7. Client receives token, stores in system keyring

Testing

Unit Tests (backend/api/auth/token_test.go)

  • Token generation with format validation
  • Consistent hashing
  • Setup code creation and expiration
  • Single-use consumption
  • Case-insensitive handling

Integration Tests (backend/api/auth_test.go)

  • All AuthService RPCs with success and error cases
  • Admin authorization enforcement
  • Duplicate name detection
  • Token filtering and expiry handling
  • Setup code validation

Test Infrastructure Updates

  • Added TokenProvider to test handler options
  • Token table cleanup between test runs
  • Generated AuthServiceClient mock

All tests pass and compile successfully.

Changes

New Files

  • backend/memory/schema/token.go - Token entity definition
  • backend/memory/schema/types/token.go - TokenType enum
  • backend/api/auth/identity.go - Identity and AuthMethod types
  • backend/api/auth/transport.go - Transport context utilities
  • backend/api/auth/token.go - TokenProvider implementation
  • backend/api/auth/interceptor.go - Authentication interceptor
  • backend/api/auth.go - AuthService handler
  • backend/api/auth/token_test.go - TokenProvider unit tests
  • backend/api/auth_test.go - AuthService integration tests
  • api/go/client/mocks/auth.connect_mock.go - Generated mock

Modified Files

  • backend/api/api.go - Integrated auth interceptor and transport context
  • backend/api/api_test.go - Added TokenProvider and Token cleanup
  • api/go/client/client.go - Added Auth() method

Generated Files

  • backend/memory/* - Ent-generated code for Token entity
  • api/go/v1/v1connect/auth.connect.go - ConnectRPC service handlers (already existed)

Backward Compatibility

✅ Local Unix socket usage unchanged (no auth required)
✅ Existing commands continue to work without modification
✅ No breaking changes to API or CLI

Furisto and others added 10 commits January 1, 2026 18:03
Create Ent schema for authentication tokens with:
- UUID primary key
- Unique name for human-readable identification
- SHA-256 token hash for secure storage
- Optional description field
- Expiration timestamp

Indexes on name and token_hash for efficient lookups during
authentication validation.

Co-authored-by: construct-agent <noreply@construct.sh>
Create Identity and Transport context utilities for request
authentication flow:
- Identity: carries authenticated subject, auth method, admin status
- AuthMethod enum: unix_socket, token, or unspecified
- Transport context: tracks connection type (unix vs tcp)

Context helpers use unexported key types to prevent collisions.

Co-authored-by: construct-agent <noreply@construct.sh>
Add TokenProvider with:
- Secure token generation using crypto/rand (32 bytes)
- SHA-256 hashing for token storage
- Token format validation (ct_ prefix + base64url)
- Setup code generation (8-char alphanumeric, XXXX-XXXX format)
- In-memory setup code storage with expiration
- Thread-safe code consumption (single-use with mutex)

Setup codes exclude ambiguous characters (0/O, 1/I) and are
case-insensitive for user convenience.

Co-authored-by: construct-agent <noreply@construct.sh>
Implement unary and streaming interceptors that:
- Allow unauthenticated access to ExchangeSetupCode
- Grant admin privileges to Unix socket connections
- Validate Bearer tokens for TCP connections
- Hash and lookup tokens in database
- Check token expiration
- Populate Identity context for downstream handlers

Returns CodeUnauthenticated for auth failures, CodeInternal
for unexpected errors.

Co-authored-by: construct-agent <noreply@construct.sh>
Extract authentication logic into shared authenticate() method
to reduce duplication between unary and streaming interceptors.
Rename methods to WrapUnary and WrapStreamingHandler to match
ConnectRPC interceptor interface conventions.

Co-authored-by: construct-agent <noreply@construct.sh>
Add handler for all auth operations:
- CreateToken: generates secure tokens with configurable expiry
- CreateSetupCode: creates short-lived exchange codes
- ListTokens: queries tokens with optional filters
- RevokeToken: deletes tokens by ID
- ExchangeSetupCode: unauthenticated bootstrap endpoint

All admin-only operations check Identity.IsAdmin from context.
ExchangeSetupCode validates uniqueness before token creation.
Proper error codes for permission, validation, and not found cases.

Co-authored-by: construct-agent <noreply@construct.sh>
Wire up authentication system:
- Create TokenProvider singleton in NewServer
- Add transport context injection via ConnContext (unix vs tcp)
- Create AuthInterceptor and apply to all handlers
- Register AuthServiceHandler with interceptor
- Pass TokenProvider through HandlerOptions

All service handlers now protected by authentication middleware.
Unix socket connections automatically granted admin privileges.

Co-authored-by: construct-agent <noreply@construct.sh>
Unit tests for TokenProvider:
- Token generation with proper format and uniqueness
- Consistent token hashing
- Token format validation
- Setup code creation with correct format
- Setup code consumption (single-use, case-insensitive)
- Setup code expiration

Integration tests for AuthService:
- CreateToken with validation and duplicate detection
- CreateSetupCode generation
- ListTokens with filters and expiry handling
- RevokeToken by ID with proper error handling
- ExchangeSetupCode validation

Update test infrastructure to include TokenProvider and
Token table cleanup between test runs.

Co-authored-by: construct-agent <noreply@construct.sh>
Add WrapStreamingClient to AuthInterceptor to fully implement
connect.Interceptor interface.

Add Auth() method to Client for accessing AuthService operations.
Include auth field in Client struct and wire up AuthServiceClient
in NewClient and mock implementations.

Generate mockgen mock for AuthServiceClient to support testing.

Fix test compilation by properly escaping quotes in error messages.

Co-authored-by: construct-agent <noreply@construct.sh>
@Furisto Furisto marked this pull request as ready for review January 3, 2026 13:30
@Furisto Furisto merged commit b353df9 into main Jan 3, 2026
1 of 2 checks passed
@Furisto Furisto deleted the auth-api branch January 3, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant