httpserver

package
v0.0.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2026 License: Apache-2.0 Imports: 67 Imported by: 0

README

HTCondor HTTP API Server

A RESTful HTTP API server for managing HTCondor jobs.

Features

  • Job Submission: Submit jobs via HTTP POST with HTCondor submit file
  • Job Queries: List and retrieve job details with ClassAd constraints and projections
  • File Transfer: Upload input files and download output files as tarballs
  • Authentication: Bearer token authentication forwarded to HTCondor schedd
  • Demo Mode: Built-in mini HTCondor setup for testing and development
  • OpenAPI: Full OpenAPI 3.0 specification for API documentation

Installation

cd cmd/htcondor-api
go build

Usage

Normal Mode (with existing HTCondor)
# Uses HTCondor configuration from environment
./htcondor-api

The server will:

  1. Read HTCondor configuration from standard locations
  2. Connect to the configured schedd
  3. Listen on port 8080 (default)
Demo Mode (standalone mini HTCondor)
# Starts mini HTCondor automatically
./htcondor-api --demo

Demo mode will:

  1. Create a temporary directory for mini HTCondor
  2. Write minimal HTCondor configuration
  3. Start condor_master as a subprocess
  4. Start the HTTP API server
  5. Clean up on Ctrl+C or SIGTERM
User Header Authentication (Demo Mode Only)

In demo mode, you can enable automatic token generation based on a custom HTTP header:

# Enable user header authentication
./htcondor-api --demo --user-header=X-Remote-User

With this option:

  • If the Authorization header is present, it's used as normal
  • If no Authorization header is present but X-Remote-User is set:
    • A signing key is automatically generated
    • A JWT token is created for the username in the header
    • This token is used to authenticate with HTCondor

This is useful for testing with reverse proxies that handle authentication and pass the username via header (e.g., Apache with mod_auth, nginx with auth_request).

Example:

# Submit a job using user header instead of bearer token
curl -X POST http://localhost:8080/api/v1/jobs \
  -H "X-Remote-User: alice" \
  -H "Content-Type: application/json" \
  -d '{"submit_file": "executable=/bin/echo\narguments=Hello\nqueue"}'

# List jobs
curl http://localhost:8080/api/v1/jobs \
  -H "X-Remote-User: alice"

Note: This feature is only available in demo mode and is intended for development/testing. In production, use proper HTCondor TOKEN authentication.

Custom Listen Address
./htcondor-api --listen :9000

API Endpoints

Job Management
Submit a Job
POST /api/v1/jobs
Authorization: Bearer <TOKEN>
Content-Type: application/json

{
  "submit_file": "executable = /bin/sleep\narguments = 60\nqueue"
}

Response:

{
  "cluster_id": 1,
  "job_ids": ["1.0"]
}
List Jobs
GET /api/v1/jobs?constraint=Owner=="user"&projection=ClusterId,ProcId,JobStatus
Authorization: Bearer <TOKEN>

Response:

{
  "jobs": [
    {
      "ClusterId": 1,
      "ProcId": 0,
      "JobStatus": 2,
      "Owner": "user"
    }
  ]
}
Get Job Details
GET /api/v1/jobs/1.0
Authorization: Bearer <TOKEN>

Response:

{
  "ClusterId": 1,
  "ProcId": 0,
  "JobStatus": 2,
  "Owner": "user",
  "Cmd": "/bin/sleep",
  ...
}
Remove Job (Not Yet Implemented)
DELETE /api/v1/jobs/1.0
Authorization: Bearer <TOKEN>
Edit Job (Not Yet Implemented)
PATCH /api/v1/jobs/1.0
Authorization: Bearer <TOKEN>
Content-Type: application/json

{
  "JobPrio": 10
}
File Transfer
Upload Job Input Files
PUT /api/v1/jobs/1.0/input
Authorization: Bearer <TOKEN>
Content-Type: application/x-tar

< input.tar

The tarball should contain the job's input files as specified in TransferInput.

Download Job Output Files
GET /api/v1/jobs/1.0/output
Authorization: Bearer <TOKEN>

> output.tar

Returns a tarball containing the job's output files.

Documentation
OpenAPI Schema
GET /openapi.json

Returns the full OpenAPI 3.0 specification.

Prometheus Metrics
GET /metrics

Returns Prometheus-formatted metrics about the HTCondor pool and the server process. Available when a collector is configured.

Example metrics:

  • htcondor_pool_machines_total - Total machines in the pool
  • htcondor_pool_cpus_total - Total CPU cores
  • htcondor_pool_cpus_used - Used CPU cores
  • htcondor_pool_memory_mb_total - Total memory in MB
  • htcondor_pool_jobs_total - Total jobs
  • process_resident_memory_bytes - Server memory usage
  • process_goroutines - Active goroutines

See ../metricsd/README.md for complete metrics documentation.

Authentication

The server supports multiple authentication methods:

1. Bearer Token Authentication

The bearer token from the HTTP Authorization header is passed to the HTCondor schedd for authentication.

# Generate a token (requires HTCondor admin access)
condor_token_create -identity user@example.com > token.txt

# Use the token in API requests
curl -H "Authorization: Bearer $(cat token.txt)" \
  http://localhost:8080/api/v1/jobs

After successful OAuth2/SSO authentication, the server sets an HTTP session cookie that can be used for subsequent API requests. This enables browser-based web UIs to authenticate users via SSO and then interact with the REST API without managing bearer tokens.

Session Cookie Features:

  • Secure, HttpOnly cookies with SameSite protection
  • Configurable TTL (default: 24 hours)
  • Automatic cleanup of expired sessions
  • Username extracted from session for HTCondor operations

How it works:

  1. User authenticates via OAuth2/SSO flow (e.g., /mcp/oauth2/authorize)
  2. After successful authentication, server sets htcondor_session cookie
  3. Browser automatically includes cookie in subsequent API requests
  4. Server extracts username from cookie and generates HTCondor token

Example workflow:

# Step 1: User authenticates via browser (redirected to IDP)
# Browser visits: http://localhost:8080/mcp/oauth2/authorize?...

# Step 2: After successful auth, session cookie is set automatically

# Step 3: Browser can now make API requests without bearer token
# The session cookie is included automatically by the browser
curl -b cookies.txt http://localhost:8080/api/v1/jobs
3. User Header Authentication (Demo Mode)

In demo mode with --user-header flag, the server can extract username from a custom HTTP header and generate tokens. See demo mode section above.

Authentication Priority

The server checks for authentication in the following order:

  1. Bearer token in Authorization header (highest priority)
  2. Session cookie (for browser-based authentication)
  3. User header (if configured, for reverse proxy authentication)

Note: Token integration is partially implemented. The token is extracted but not yet fully integrated into the schedd authentication layer. See HTTP_API_TODO.md for details.

Configuration

HTCondor Configuration Parameters

The HTTP API server reads configuration from HTCondor's configuration system. Configuration can be placed in /etc/condor/config.d/ or any HTCondor configuration file.

HTTP Server Parameters
# Listen address (default: :8080)
HTTP_API_LISTEN_ADDR = :8443

# TLS/HTTPS configuration (optional - both required for TLS)
HTTP_API_TLS_CERT = /etc/condor/certs/server.crt
HTTP_API_TLS_KEY = /etc/condor/certs/server.key

# HTTP timeout configuration (optional, duration strings)
HTTP_API_READ_TIMEOUT = 30s      # Default: 30s
HTTP_API_WRITE_TIMEOUT = 30s     # Default: 30s
HTTP_API_IDLE_TIMEOUT = 2m       # Default: 120s

# Session configuration (optional)
HTTP_API_SESSION_TTL = 24h       # Default: 24h (session cookie lifetime)

# User header for authentication (optional)
HTTP_API_USER_HEADER = X-Forwarded-User

# JWT signing key path (optional, demo mode only)
HTTP_API_SIGNING_KEY = /etc/condor/keys/jwt_signing.key
MCP OAuth2 Configuration

Model Context Protocol (MCP) endpoints require OAuth2 authentication. Enable MCP support with:

# Enable MCP endpoints (default: false)
HTTP_API_ENABLE_MCP = true

# OAuth2 database path for storing clients and tokens
# Default: $(LOCAL_DIR)/oauth2.db or /var/lib/condor/oauth2.db
HTTP_API_OAUTH2_DB_PATH = /var/lib/condor/oauth2.db

# OAuth2 issuer URL (default: https://$(FULL_HOSTNAME) with non-standard port appended)
# If not explicitly set, the server will append the listen port if it's non-standard (not 443)
HTTP_API_OAUTH2_ISSUER = https://htcondor.example.com

# OIDC/SSO Configuration (optional, for external identity provider)

# Option 1: Use OIDC Discovery (recommended)
# The server will automatically discover auth and token endpoints from the provider
HTTP_API_OAUTH2_IDP = https://idp.example.com

# Option 2: Specify endpoints explicitly
# HTTP_API_OAUTH2_AUTH_URL = https://idp.example.com/auth/realms/master/protocol/openid-connect/auth
# HTTP_API_OAUTH2_TOKEN_URL = https://idp.example.com/auth/realms/master/protocol/openid-connect/token

# OAuth2 client credentials for SSO provider (e.g., Keycloak, Okta, Google)
HTTP_API_OAUTH2_CLIENT_ID = htcondor-api-client

# Client secret is read from a file (not directly in config for security)
# HTCondor configuration is considered public, so secrets must be in separate files
HTTP_API_OAUTH2_CLIENT_SECRET_FILE = /etc/condor/secrets/oauth2_client_secret

# Redirect URL for OAuth2 callback (default: derived from issuer + /oauth2/callback)
# Only set this if you need a different callback URL
# HTTP_API_OAUTH2_REDIRECT_URL = https://htcondor.example.com/oauth2/callback

# Username claim name (default: "sub")
# Specify which claim in the OAuth2 token contains the username
# Common alternatives: "preferred_username", "email", "username"
HTTP_API_OAUTH2_USERNAME_CLAIM = preferred_username

# User info endpoint URL (required for SSO integration with group-based access control)
# This endpoint should return user information including group membership
HTTP_API_OAUTH2_USERINFO_URL = https://idp.example.com/userinfo

# Claim name for groups in the userinfo response (default: "groups")
# The groups can be returned as either a JSON array or space-delimited string
HTTP_API_OAUTH2_GROUPS_CLAIM = groups

# Group-Based Access Control (optional)
# Control access to MCP endpoints and scope granting based on group membership

# Required group for any MCP access (if not set, all authenticated users can access)
# Users without this group will receive an "access_denied" error
HTTP_API_MCP_ACCESS_GROUP = mcp-users

# Required group for mcp:read scope (if not set, no users get read access by default)
# Users in this group will receive the mcp:read scope
HTTP_API_MCP_READ_GROUP = mcp-read

# Required group for mcp:write scope (if not set, no users get write access by default)
# Users in this group will receive both mcp:read and mcp:write scopes
HTTP_API_MCP_WRITE_GROUP = mcp-write

OIDC Discovery:

When HTTP_API_OAUTH2_IDP is set, the server will fetch the OIDC configuration from:

<IDP_URL>/.well-known/openid-configuration

This automatically discovers the authorization and token endpoints, making configuration easier and more maintainable. If discovery fails or you prefer explicit configuration, use HTTP_API_OAUTH2_AUTH_URL and HTTP_API_OAUTH2_TOKEN_URL instead.

Client Secret File:

Create a file with your OAuth2 client secret:

echo "your-secret-here" > /etc/condor/secrets/oauth2_client_secret
chmod 600 /etc/condor/secrets/oauth2_client_secret
chown condor:condor /etc/condor/secrets/oauth2_client_secret

User Header Authentication:

If HTTP_API_USER_HEADER is configured (e.g., from a reverse proxy), the user identity is taken from that header instead of the OAuth2 token subject. This allows integration with existing authentication systems:

# User identity from reverse proxy header
HTTP_API_USER_HEADER = X-Remote-User

# When both are configured:
# - User identity comes from the header (e.g., X-Remote-User: alice)
# - OAuth2 is still used for authorization and scoping
# - HTCondor tokens are generated for the header-provided username

This is useful when running behind Apache with authentication modules, nginx with auth_request, or similar setups where user authentication is handled upstream.

OIDC/SSO Integration:

When OIDC/SSO parameters are configured, the HTTP API server can:

  1. Redirect users to your identity provider for authentication
  2. Handle OAuth2 authorization code flow
  3. Exchange authorization codes for access tokens
  4. Generate HTCondor tokens based on authenticated identity (or user header if configured)
  5. Enable MCP clients to authenticate through your existing SSO infrastructure

Example OIDC Providers:

All major OIDC providers support automatic discovery. Set HTTP_API_OAUTH2_IDP to the issuer URL, and the server will automatically discover the endpoints:

  • Keycloak: Popular open-source identity and access management solution

    • IDP URL: https://keycloak.example.com/auth/realms/{realm}
    • Or explicit - Auth URL: https://keycloak.example.com/auth/realms/{realm}/protocol/openid-connect/auth
    • Token URL: https://keycloak.example.com/auth/realms/{realm}/protocol/openid-connect/token
  • Okta: Enterprise identity service

    • IDP URL: https://{domain}.okta.com/oauth2/default
    • Or explicit - Auth URL: https://{domain}.okta.com/oauth2/default/v1/authorize
    • Token URL: https://{domain}.okta.com/oauth2/default/v1/token
  • Google: Google OAuth2

    • IDP URL: https://accounts.google.com
    • Or explicit - Auth URL: https://accounts.google.com/o/oauth2/v2/auth
    • Token URL: https://oauth2.googleapis.com/token
  • GitHub: GitHub OAuth2 (does not support OIDC discovery, use explicit URLs)

    • Auth URL: https://github.com/login/oauth/authorize
    • Token URL: https://github.com/login/oauth/access_token
  • Azure AD: Microsoft Azure Active Directory

    • IDP URL: https://login.microsoftonline.com/{tenant}/v2.0
    • Or explicit - Auth URL: https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
    • Token URL: https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token

Setting up OIDC Provider (Example with Keycloak):

  1. Create a new client in your OIDC provider with:

    • Client ID: htcondor-api-client
    • Client Protocol: openid-connect
    • Access Type: confidential
    • Valid Redirect URIs: https://htcondor.example.com/oauth2/callback
    • Web Origins: https://htcondor.example.com
  2. Configure scopes: openid, profile, email

  3. Note the client secret from the credentials tab

  4. Add the configuration to HTCondor config file

For complete MCP documentation including OAuth2 flows, see the MCP integration test in httpserver/mcp_integration_test.go.

Schedd Configuration
# Schedd configuration (required for normal mode)
SCHEDD_NAME = local
SCHEDD_HOST = 127.0.0.1
SCHEDD_PORT = 9618
Collector Configuration (Optional - for Metrics)
# Collector configuration (optional, enables /metrics endpoint)
COLLECTOR_HOST = 127.0.0.1
COLLECTOR_PORT = 9618

# Metrics cache TTL (optional, default: 10s)
METRICS_CACHE_TTL = 10s

When collector configuration is provided, the HTTP server automatically:

  1. Registers pool and process metrics collectors
  2. Exposes metrics at /metrics in Prometheus format
  3. Caches metrics according to configured TTL
Configuration Examples
Basic HTTP Server

Create /etc/condor/config.d/99-http-api.config:

HTTP_API_LISTEN_ADDR = :8080

Start the server:

./htcondor-api --mode=normal
HTTPS Server with TLS

Create /etc/condor/config.d/99-http-api.config:

HTTP_API_LISTEN_ADDR = :8443
HTTP_API_TLS_CERT = /etc/condor/certs/server.crt
HTTP_API_TLS_KEY = /etc/condor/certs/server.key
HTTP_API_READ_TIMEOUT = 45s
HTTP_API_WRITE_TIMEOUT = 45s
HTTP_API_IDLE_TIMEOUT = 5m

Generate self-signed certificates (for testing):

openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt \
  -days 365 -nodes -subj "/CN=localhost"

Start the server:

./htcondor-api --mode=normal

The server will listen on port 8443 with HTTPS.

MCP with OAuth2 and OIDC/SSO Integration

Create /etc/condor/config.d/99-http-api-mcp.config:

# Enable MCP endpoints
HTTP_API_ENABLE_MCP = true

# HTTPS configuration (required for production OAuth2)
HTTP_API_LISTEN_ADDR = :8443
HTTP_API_TLS_CERT = /etc/condor/certs/server.crt
HTTP_API_TLS_KEY = /etc/condor/certs/server.key

# OAuth2 provider configuration
HTTP_API_OAUTH2_DB_PATH = /var/lib/condor/oauth2.db
# Issuer is automatically constructed as https://$(FULL_HOSTNAME):8443
# since port 8443 is non-standard, or set explicitly:
# HTTP_API_OAUTH2_ISSUER = https://htcondor.example.com:8443

# OIDC/SSO provider integration using discovery (recommended)
HTTP_API_OAUTH2_IDP = https://keycloak.example.com/auth/realms/htcondor
HTTP_API_OAUTH2_CLIENT_ID = htcondor-mcp-client
HTTP_API_OAUTH2_CLIENT_SECRET_FILE = /etc/condor/secrets/oauth2_client_secret

# Redirect URL is automatically derived as $(HTTP_API_OAUTH2_ISSUER)/oauth2/callback
# Override only if needed:
# HTTP_API_OAUTH2_REDIRECT_URL = https://htcondor.example.com:8443/oauth2/callback

# HTCondor token generation for authenticated users
HTTP_API_SIGNING_KEY = /etc/condor/keys/POOL
TRUST_DOMAIN = htcondor.example.com
UID_DOMAIN = example.com

# Optional: Use user header from reverse proxy for identity
# HTTP_API_USER_HEADER = X-Remote-User

Create the OAuth2 client secret file:

mkdir -p /etc/condor/secrets
echo "your-keycloak-client-secret" > /etc/condor/secrets/oauth2_client_secret
chmod 600 /etc/condor/secrets/oauth2_client_secret
chown condor:condor /etc/condor/secrets/oauth2_client_secret

OAuth2 Endpoints Available:

  • GET /oauth2/authorize - OAuth2 authorization endpoint (redirects to OIDC provider)
  • POST /oauth2/token - OAuth2 token endpoint (exchanges auth code for access token)
  • POST /oauth2/introspect - OAuth2 token introspection endpoint
  • POST /mcp - MCP JSON-RPC endpoint (requires OAuth2 bearer token)

OAuth2 Flow for MCP Clients:

  1. Client initiates OAuth2 authorization code flow at /oauth2/authorize
  2. User is redirected to OIDC provider (e.g., Keycloak) for authentication
  3. After successful authentication, user is redirected back with authorization code
  4. Client exchanges authorization code for access token at /oauth2/token
  5. Client uses access token to make MCP requests to /mcp endpoint

Example MCP Request with OAuth2:

# Step 1: Get authorization code (typically done in browser)
# User visits: https://htcondor.example.com/oauth2/authorize?client_id=htcondor-mcp-client&response_type=code&redirect_uri=https://htcondor.example.com/oauth2/callback&scope=openid

# Step 2: Exchange code for token
curl -X POST https://htcondor.example.com/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=https://htcondor.example.com/oauth2/callback&client_id=htcondor-mcp-client&client_secret=your-secret-here"

# Response:
# {
#   "access_token": "eyJhbGc...",
#   "token_type": "Bearer",
#   "expires_in": 3600
# }

# Step 3: Use access token for MCP requests
curl -X POST https://htcondor.example.com/mcp \
  -H "Authorization: Bearer eyJhbGc..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "job.submit",
    "params": {
      "submit_file": "executable=/bin/echo\narguments=Hello from MCP\nqueue"
    },
    "id": 1
  }'

For detailed MCP protocol documentation, see mcpserver/README.md.

Behind Reverse Proxy

If running behind a reverse proxy that sets authentication headers:

HTTP_API_LISTEN_ADDR = 127.0.0.1:8080
HTTP_API_USER_HEADER = X-Forwarded-User
HTTP_API_READ_TIMEOUT = 60s
HTTP_API_WRITE_TIMEOUT = 60s
Command Line Options

Command-line flags override HTCondor configuration:

# Override listen address
./htcondor-api --mode=normal --listen=:9090

# Override user header
./htcondor-api --mode=normal --user-header=X-Auth-User
Demo Mode

Demo mode uses a minimal configuration stored in a temporary directory. The configuration includes:

  • All daemons running locally (MASTER, COLLECTOR, NEGOTIATOR, SCHEDD, STARTD)
  • TOKEN authentication enabled
  • File transfer enabled
  • Jobs kept in queue for 24 hours after completion
# Start in demo mode
./htcondor-api --demo

# Demo mode with custom listen address
./htcondor-api --demo --listen=:9090

For complete configuration examples, see examples/http_api_config/.

Examples

See the examples/ directory in the repository for:

  • Python client examples
  • Shell script examples
  • Integration examples

Development

Project Structure
httpserver/
  ├── server.go       # HTTP server setup
  ├── routes.go       # Route configuration
  ├── handlers.go     # Request handlers
  ├── auth.go         # Authentication helpers
  └── openapi.go      # OpenAPI schema

cmd/htcondor-api/
  └── main.go         # Main binary with demo mode
Testing
# Run with demo mode for testing
go run cmd/htcondor-api/main.go --demo

# In another terminal, test the API
curl http://localhost:8080/openapi.json
Adding Features

See HTTP_API_TODO.md for a list of planned features and implementation notes.

Current Status

The HTTP API server implements the following features (see HTTP_API_TODO.md for complete details):

Completed:

  • Bearer token authentication integrated with HTCondor schedd
  • Job submission via HTTP POST
  • Job queries with constraint and projection support
  • Individual job removal (DELETE /api/v1/jobs/{id})
  • Individual job editing (PATCH /api/v1/jobs/{id})
  • Bulk job operations (DELETE/PATCH /api/v1/jobs with constraints)
  • File transfer (upload input, download output)
  • Configuration via HTCondor config system
  • TLS/HTTPS support
  • Configurable HTTP timeouts

Pending:

  • Job history support (querying completed jobs)
  • Job status monitoring (SSE/WebSocket for real-time updates)
  • Enhanced demo mode (auto-generated tokens, test jobs)
  • Metrics and monitoring (Prometheus endpoint)

See HTTP_API_TODO.md for detailed implementation notes and examples.

License

See the repository LICENSE file for details.

Contributing

Contributions are welcome! Please:

  1. Check HTTP_API_TODO.md for planned features
  2. Follow existing code patterns
  3. Add tests for new functionality
  4. Update documentation

References

Documentation

Overview

Package httpserver provides HTTP API handlers for HTCondor operations.

Package httpserver provides HTTP API handlers for HTCondor operations.

Index

Constants

View Source
const (
	InteractiveWatchdogPollSec      = 30
	InteractiveWatchdogFreshnessSec = 120
)

Watchdog timing.

The script polls every InteractiveWatchdogPollSec; if .heartbeat is older than InteractiveWatchdogFreshnessSec it exits. The webapp side sends heartbeats every interactiveHeartbeatIntervalSec (handlers_ssh.go) when the user has typed within interactiveHeartbeatIdleWindowSec.

Defaults give a ~120s eviction window once the user goes idle, with 30s polling on each end. Tuned for "tab forgotten" not "user thinking".

Variables

View Source
var (
	ErrAuthorizationPending = &fosite.RFC6749Error{
		ErrorField:       "authorization_pending",
		DescriptionField: "The authorization request is still pending",
		CodeField:        http.StatusBadRequest,
	}
	ErrSlowDown = &fosite.RFC6749Error{
		ErrorField:       "slow_down",
		DescriptionField: "Client is polling too frequently",
		CodeField:        http.StatusBadRequest,
	}
	ErrExpiredToken = &fosite.RFC6749Error{
		ErrorField:       "expired_token",
		DescriptionField: "The device code has expired",
		CodeField:        http.StatusBadRequest,
	}
)

Device flow error codes (RFC 8628)

Functions

func ConfigureSecurityForCollectorPing added in v0.0.11

func ConfigureSecurityForCollectorPing(token, serverName string) (*security.SecurityConfig, error)

ConfigureSecurityForCollectorPing builds a SecurityConfig used solely by the periodic collector ping. The collector ping is read-only — we just need *some* mutually agreeable handshake — so this offers both TOKEN and SSL. That's useful when the daemon's token does not match the collector's IssuerKeys (an issuer rotation, a misconfigured TrustDomain, etc.): SSL keeps /readyz green via a path that has nothing to do with JWT signing. The schedd path — which DOES need the token's identity for authz — keeps using TOKEN only.

SSL is always offered (even with no client cert/key on disk) because many collectors permit anonymous SSL: the client only verifies the server's cert and connects as ANONYMOUS@…, which is enough for a read-only ping. Cedar's SSL auth handles empty CertFile/KeyFile as "no client cert presented" and empty CAFile as "use the system trust store" — see cedar/security/ssl_auth.go and cmd/ssl-test/main.go.

serverName is used by cedar's SSL handshake for hostname/SAN verification. Without it, cedar falls back to the literal string "unknown" and verification fails ("certificate is valid for host.example.com, ..., not unknown"). Pass the bare hostname of the collector address — see hostFromCondorAddress in handler.go.

`token` may be empty; in that case only SSL is offered.

func ConfigureSecurityForToken

func ConfigureSecurityForToken(token string) (*security.SecurityConfig, error)

ConfigureSecurityForToken configures security settings to use the provided token This is a helper function to set up cedar's security configuration for TOKEN authentication

func ConfigureSecurityForTokenWithCache added in v0.0.3

func ConfigureSecurityForTokenWithCache(token string, sessionCache *security.SessionCache) (*security.SecurityConfig, error)

ConfigureSecurityForTokenWithCache configures security settings with an optional session cache If sessionCache is nil, the global cache will be used

func ConfigureSecurityForTokenWithCacheAndFallback added in v0.0.4

func ConfigureSecurityForTokenWithCacheAndFallback(token string, sessionCache *security.SessionCache, allowFSFallback bool) (*security.SecurityConfig, error)

ConfigureSecurityForTokenWithCacheAndFallback configures security settings with optional session cache and optional FS authentication fallback.

allowFSFallback semantics:

  • true (user-header mode): the token is generated locally per request and not signed with anything the schedd recognises, so we APPEND FS as a fallback for use against a same-host schedd where the OS-user identity is acceptable.

  • false (session/JWT mode): the token IS signed by us with the pool's signing key, the schedd validates it, and its `sub` claim is the user we want recorded as the job Owner. We therefore REMOVE FS from the offered methods, so the schedd can't pick it during negotiation. (Cedar's negotiation walks the server's preference order and selects the first method also offered by the client; HTCondor's default lists FS first, so leaving FS in the client's list lets FS win on a same-host schedd, and the schedd then records the OS user instead of the token's identity. We saw this in session_integration_test.go: jobs submitted via session cookie were owned by `vscode` — the test runner's UID — not by the JWT subject `testuser@trust.domain`.)

Authentication methods otherwise come from SEC_CLIENT_AUTHENTICATION_METHODS / SEC_DEFAULT_AUTHENTICATION_METHODS in the loaded HTCondor configuration. This was previously a hardcoded `[TOKEN]` list, which broke any pool that expects SSL alongside IDTOKENS.

Implementation: delegates to htcondor.NewClientSecurityConfig for the configured-methods-aware base, then applies the FS rule above. Other call sites (file_transfer, schedd_ssh, mcpserver) use NewClientSecurityConfig directly; the httpserver-only allowFSFallback knob lives here so we don't drag it into the root package's API.

func DefaultIDPSession added in v0.0.4

func DefaultIDPSession(username string) *openid.DefaultSession

DefaultIDPSession creates a default OpenID Connect session for IDP

func DefaultOpenIDConnectSession added in v0.0.3

func DefaultOpenIDConnectSession(username string) *openid.DefaultSession

DefaultOpenIDConnectSession creates a default OpenID Connect session

func GenerateSigningKey

func GenerateSigningKey() ([]byte, error)

GenerateSigningKey generates a new signing key for token generation Returns the key content as bytes

func GetScheddWithToken

func GetScheddWithToken(ctx context.Context, schedd *htcondor.Schedd) (*htcondor.Schedd, error)

GetScheddWithToken creates a schedd connection configured with token authentication This wraps the schedd to use token authentication from context

func GetSecurityConfigFromToken

func GetSecurityConfigFromToken(ctx context.Context) (*security.SecurityConfig, error)

GetSecurityConfigFromToken retrieves the token from context and creates a SecurityConfig This is a convenience function for HTTP handlers to convert context token to SecurityConfig

func GetTokenFromContext

func GetTokenFromContext(ctx context.Context) (string, bool)

GetTokenFromContext retrieves the token from the context

func WithToken

func WithToken(ctx context.Context, token string) context.Context

WithToken creates a context that includes authentication token information This sets up the security configuration for cedar to use TOKEN authentication

Types

type AdminClient added in v0.0.11

type AdminClient struct {
	ID            string    `json:"id"`
	RedirectURIs  []string  `json:"redirect_uris,omitempty"`
	GrantTypes    []string  `json:"grant_types,omitempty"`
	ResponseTypes []string  `json:"response_types,omitempty"`
	Scopes        []string  `json:"scopes,omitempty"`
	Public        bool      `json:"public"`
	CreatedAt     time.Time `json:"created_at"`
}

AdminClient is the SPA-facing shape for an OAuth2 client. We mirror only the fields useful for an "audit + cleanup" UI; secrets are never returned (they're hashed in storage anyway, but we still strip them out of the response shape on principle).

type AdminLogsResponse added in v0.0.11

type AdminLogsResponse struct {
	Enabled bool                  `json:"enabled"`
	Entries []logging.BufferEntry `json:"entries"`
}

AdminLogsResponse wraps the buffer entries with a hint when the buffer hasn't been initialized — the SPA shows a different empty state for "no logs yet" vs "feature not wired up".

type AdminToken added in v0.0.11

type AdminToken struct {
	Kind            string    `json:"kind"` // "access" or "refresh"
	SignaturePrefix string    `json:"signature_prefix"`
	ClientID        string    `json:"client_id"`
	Subject         string    `json:"subject,omitempty"`
	Scopes          []string  `json:"scopes,omitempty"`
	Active          bool      `json:"active"`
	RequestedAt     time.Time `json:"requested_at"`
	ExpiresAt       time.Time `json:"expires_at,omitempty"`
}

AdminToken is the SPA-facing shape for an OAuth2 access/refresh token row. We never expose the raw token signature — only its prefix as a fingerprint, so admins can correlate against logs without being able to use the token themselves.

type AdvertiseRequest added in v0.0.4

type AdvertiseRequest struct {
	Ad      *classad.ClassAd `json:"ad,omitempty"`       // Single ad (JSON body)
	Command string           `json:"command,omitempty"`  // Optional UPDATE command (e.g., "UPDATE_STARTD_AD")
	WithAck bool             `json:"with_ack,omitempty"` // Request acknowledgment
}

AdvertiseRequest represents a request to advertise to the collector

type AdvertiseResponse added in v0.0.4

type AdvertiseResponse struct {
	Success   bool     `json:"success"`
	Message   string   `json:"message,omitempty"`
	Succeeded int      `json:"succeeded"`        // Number of ads successfully advertised
	Failed    int      `json:"failed"`           // Number of ads that failed
	Errors    []string `json:"errors,omitempty"` // Error messages for failed ads
}

AdvertiseResponse represents the response from advertise

type AuthMeResponse added in v0.0.11

type AuthMeResponse struct {
	Authenticated bool     `json:"authenticated"`
	Username      string   `json:"username,omitempty"`
	Groups        []string `json:"groups,omitempty"`
	IsAdmin       bool     `json:"is_admin"`
}

AuthMeResponse describes the currently-authenticated browser session.

The Web UI uses this as its single source of truth for "is the user logged in", who they are, and whether to render admin pages. It is intentionally looser than /api/v1/whoami: it always returns 200 (with Authenticated=false when there is no session) so the SPA can render a landing page without going through error-handling.

type CollectorAdsResponse added in v0.0.3

type CollectorAdsResponse struct {
	Ads []*classad.ClassAd `json:"ads"`
}

CollectorAdsResponse represents collector ads listing response

type Config

type Config struct {
	ListenAddr      string              // Address to listen on (e.g., ":8080")
	ScheddName      string              // Schedd name
	ScheddAddr      string              // Schedd address (e.g., "127.0.0.1:9618"). If empty, discovered from collector.
	UserHeader      string              // HTTP header to extract username from (optional)
	SigningKeyPath  string              // Path to token signing key (optional, for token generation)
	TrustDomain     string              // Trust domain for token issuer (optional; only used if UserHeader is set)
	UIDDomain       string              // UID domain for generated token username (optional; only used if UserHeader is set)
	HTTPBaseURL     string              // Base URL for HTTP API (e.g., "http://localhost:8080") for generating file download links in MCP responses
	TLSCertFile     string              // Path to TLS certificate file (optional, enables HTTPS)
	TLSKeyFile      string              // Path to TLS key file (optional, enables HTTPS)
	TLSCACertFile   string              // Path to TLS CA certificate file (optional, for trusting self-signed certs)
	ReadTimeout     time.Duration       // HTTP read timeout (default: 30s)
	WriteTimeout    time.Duration       // HTTP write timeout (default: 30s)
	IdleTimeout     time.Duration       // HTTP idle timeout (default: 120s)
	Collector       *htcondor.Collector // Collector for metrics (optional)
	EnableMetrics   bool                // Enable /metrics endpoint (default: true if Collector is set)
	MetricsCacheTTL time.Duration       // Metrics cache TTL (default: 10s)
	Logger          *logging.Logger     // Logger instance (optional, creates default if nil)
	JupyterWorkDir  string              // Per-instance scratch dir for JupyterLab submission artifacts; default <TempDir>/htcondor-api-jupyter

	// Batch-submission template paths.
	TemplateGlobalPath string // Optional YAML file with operator-curated templates
	// TemplateUserStoreDBPath is deprecated; the templates store
	// shares the unified DBPath. Kept so existing callers compile.
	TemplateUserStoreDBPath string //nolint:unused // back-compat; ignored.
	EnableMCP               bool   // Enable MCP endpoints with OAuth2 (default: false)
	// DBPath is the unified SQLite database file. See HandlerConfig.DBPath.
	DBPath string
	// KEKFilePath enables envelope encryption for long-lived secrets
	// in the DB. See HandlerConfig.KEKFilePath.
	KEKFilePath string
	// OAuth2DBPath is the legacy name for DBPath; kept for back-compat.
	OAuth2DBPath        string
	OAuth2Issuer        string   // OAuth2 issuer URL (default: listen address)
	OAuth2ClientID      string   // OAuth2 client ID for SSO (optional)
	OAuth2ClientSecret  string   // OAuth2 client secret for SSO (optional)
	OAuth2AuthURL       string   // OAuth2 authorization URL for SSO (optional)
	OAuth2TokenURL      string   // OAuth2 token URL for SSO (optional)
	OAuth2RedirectURL   string   // OAuth2 redirect URL for SSO (optional)
	OAuth2UserInfoURL   string   // OAuth2 user info endpoint for SSO (optional)
	OAuth2Scopes        []string // OAuth2 scopes to request (default: ["openid", "profile", "email"])
	OAuth2UsernameClaim string   // Claim name for username in token (default: "sub")
	OAuth2GroupsClaim   string   // Claim name for groups in user info (default: "groups")
	// OAuth2AccessTokenLifespan / OAuth2RefreshTokenLifespan control how long the
	// embedded MCP issuer's tokens are valid. Zero means "use the package default"
	// (1h access, 30d refresh). RefreshTokenLifespan must be >= AccessTokenLifespan.
	OAuth2AccessTokenLifespan  time.Duration
	OAuth2RefreshTokenLifespan time.Duration
	MCPAccessGroup             string // Group required for any MCP access (empty = all authenticated)
	MCPReadGroup               string // Group required for read operations (empty = all have read)
	MCPWriteGroup              string // Group required for write operations (empty = all have write)
	MCPInstructions            string // Server-level instructions provided to all MCP agents (e.g., AP-specific guidance)
	WebUIAdminGroup            string // Group required for Web UI admin pages (empty disables admin UI). Configurable via HTTP_API_WEBUI_ADMIN_GROUP.
	EnableIDP                  bool   // Enable built-in IDP (always enabled in demo mode)
	// IDPDBPath is deprecated; the IDP shares the unified DBPath.
	IDPDBPath string //nolint:unused // back-compat; ignored.
	IDPIssuer string // IDP issuer URL (default: listen address)
	// IDPAccessTokenLifespan / IDPRefreshTokenLifespan: see OAuth2*Lifespan above.
	IDPAccessTokenLifespan  time.Duration
	IDPRefreshTokenLifespan time.Duration
	SessionTTL              time.Duration        // HTTP session TTL (default: 24h)
	HTCondorConfig          *config.Config       // HTCondor configuration (optional, used for LOCAL_DIR default)
	PingInterval            time.Duration        // Interval for periodic daemon pings (default: 1 minute, 0 = disabled)
	StreamBufferSize        int                  // Buffer size for streaming queries (default: 100)
	StreamWriteTimeout      time.Duration        // Write timeout for streaming queries (default: 5s)
	Token                   string               // Token for daemon authentication (optional)
	Credd                   htcondor.CreddClient // Optional credd client; defaults to in-memory implementation
}

Config holds server configuration

type DashboardResponse added in v0.0.11

type DashboardResponse struct {
	Username     string         `json:"username"`
	JobsByStatus map[string]int `json:"jobs_by_status"`
	JobsTotal    int            `json:"jobs_total"`
}

DashboardResponse summarizes the user's queue at the AP. It is a minimal shape on purpose; we'll grow it (transfer history, recent completions, user-level quota) in PR (b)/(c) once the SPA has the basics.

type DeviceAuthorizationResponse added in v0.0.4

type DeviceAuthorizationResponse struct {
	DeviceCode              string `json:"device_code"`
	UserCode                string `json:"user_code"`
	VerificationURI         string `json:"verification_uri"`
	VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
	ExpiresIn               int    `json:"expires_in"`
	Interval                int    `json:"interval,omitempty"`
}

DeviceAuthorizationResponse represents the response from device authorization endpoint

type DeviceCodeHandler added in v0.0.4

type DeviceCodeHandler struct {
	// contains filtered or unexported fields
}

DeviceCodeHandler implements the OAuth 2.0 Device Authorization Grant (RFC 8628)

func NewDeviceCodeHandler added in v0.0.4

func NewDeviceCodeHandler(storage *OAuth2Storage, config *fosite.Config) *DeviceCodeHandler

NewDeviceCodeHandler creates a new device code handler

func (*DeviceCodeHandler) HandleDeviceAccessRequest added in v0.0.4

func (h *DeviceCodeHandler) HandleDeviceAccessRequest(ctx context.Context, deviceCode string, session fosite.Session) (fosite.Requester, error)

HandleDeviceAccessRequest handles token requests with device_code grant type

func (*DeviceCodeHandler) HandleDeviceAuthorizationRequest added in v0.0.4

func (h *DeviceCodeHandler) HandleDeviceAuthorizationRequest(ctx context.Context, client fosite.Client, scopes []string) (*DeviceAuthorizationResponse, error)

HandleDeviceAuthorizationRequest handles the device authorization endpoint

type ErrorResponse

type ErrorResponse struct {
	Error   string `json:"error"`
	Message string `json:"message,omitempty"`
	Code    int    `json:"code"`
}

ErrorResponse represents an error response body

type Handler added in v0.0.7

type Handler struct {
	// contains filtered or unexported fields
}

Handler represents the HTTP API handler that can be embedded in any HTTP server

func NewHandler added in v0.0.7

func NewHandler(cfg HandlerConfig) (*Handler, error)

NewHandler creates a new HTTP API handler that can be embedded in any HTTP server

func (*Handler) GetOAuth2Provider added in v0.0.7

func (h *Handler) GetOAuth2Provider() *OAuth2Provider

GetOAuth2Provider returns the OAuth2 provider (for testing)

func (*Handler) GetSchedd added in v0.0.7

func (h *Handler) GetSchedd() *htcondor.Schedd

GetSchedd returns the current schedd instance (thread-safe)

func (*Handler) ServeHTTP added in v0.0.7

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler interface.

Every request flows through the metrics middleware first so the HTTP request counters / duration histogram / in-flight gauge cover every route uniformly — not just the ones we remembered to wrap in setupRoutes. The middleware short-circuits /metrics itself to avoid Prometheus scrapes self-instrumenting.

func (*Handler) SetupRoutes added in v0.0.7

func (h *Handler) SetupRoutes(setupFunc func(*http.ServeMux))

SetupRoutes sets up the HTTP routes on the handler's multiplexer This should be called by Server.NewServer or by users who create a Handler directly

func (*Handler) Start added in v0.0.7

func (h *Handler) Start(ctx context.Context, ln net.Listener, protocol string) error

Start initializes the handler and starts background goroutines. The provided context controls the handler's lifetime - when the context is cancelled, the handler will gracefully shut down all background goroutines.

This method should be called by Server.Start() or Server.StartTLS() before serving requests.

func (*Handler) Stop added in v0.0.7

func (h *Handler) Stop(ctx context.Context) error

Stop gracefully stops all background goroutines and closes providers. This method is called when the handler's context is cancelled (via Server.Shutdown). The background goroutines are responsible for watching their context and exiting when done.

func (*Handler) UpdateOAuth2RedirectURL added in v0.0.7

func (h *Handler) UpdateOAuth2RedirectURL(redirectURL string)

UpdateOAuth2RedirectURL updates the OAuth2 redirect URL for SSO integration

func (*Handler) UpdateSchedd added in v0.0.7

func (h *Handler) UpdateSchedd(newAddress string)

UpdateSchedd updates the schedd instance with a new address (thread-safe). On change, logs both addresses, the age of the previous address, and — when both addresses are shared-port — the old and new sock= IDs so it's obvious whether a schedd restart drove the update (sock= changed) versus a network-level address shift (host:port changed but sock= stable).

Always sets scheddAddrLastConfirmedAt to now: the caller has just talked to the collector successfully, regardless of whether the address differs from the cached value.

type HandlerConfig added in v0.0.7

type HandlerConfig struct {
	ScheddName      string              // Schedd name
	ScheddAddr      string              // Schedd address (e.g., "127.0.0.1:9618"). If empty, discovered from collector.
	UserHeader      string              // HTTP header to extract username from (optional)
	SigningKeyPath  string              // Path to token signing key (optional, for token generation)
	TrustDomain     string              // Trust domain for token issuer (optional; only used if UserHeader is set)
	UIDDomain       string              // UID domain for generated token username (optional; only used if UserHeader is set)
	HTTPBaseURL     string              // Base URL for HTTP API (e.g., "http://localhost:8080") for generating file download links in MCP responses
	TLSCACertFile   string              // Path to TLS CA certificate file (optional, for trusting self-signed certs)
	Collector       *htcondor.Collector // Collector for metrics (optional)
	EnableMetrics   bool                // Enable /metrics endpoint (default: true if Collector is set)
	MetricsCacheTTL time.Duration       // Metrics cache TTL (default: 10s)
	Logger          *logging.Logger     // Logger instance (optional, creates default if nil)
	EnableMCP       bool                // Enable MCP endpoints with OAuth2 (default: false)

	// DBPath is the unified SQLite database file backing OAuth2/MCP
	// storage, the embedded IDP, browser sessions, and user-saved
	// batch-submission templates. Defaults to LOCAL_DIR/htcondor-api.db
	// (or /var/lib/condor/htcondor-api.db when LOCAL_DIR is unset).
	// Configure via HTTP_API_DB_PATH.
	DBPath string

	// KEKFilePath is the path to a file holding the master Key
	// Encryption Key used to envelope-encrypt long-lived secrets in
	// the application database (the OAuth2 / IDP issuer's RSA
	// signing key, fosite's HMAC GlobalSecret). Configured via
	// HTTP_API_KEK_FILE — the FILE PATH lives in HTCondor config,
	// the KEY BYTES never do (HTCondor treats config values as
	// public).
	//
	// Empty disables encryption: secrets are stored in plaintext as
	// before. When set, the file must contain exactly 32 raw bytes
	// or a 32-byte hex string and must be 0600/0400. See
	// httpserver/appdb/seal for the design.
	KEKFilePath string

	// OAuth2DBPath is a deprecated alias for DBPath kept so existing
	// in-process embedders (and the test suite) keep compiling without
	// a wholesale rename. NewHandler honors it only when DBPath is
	// empty. The cmd-line wrapper does NOT bridge HTTP_API_OAUTH2_DB_PATH
	// into this field — pointing the unified DB at a pre-unification
	// oauth.db is a guaranteed crash-loop (the legacy schema conflicts
	// with goose 0001_init.sql), and the wrapper logs a deprecation
	// warning instead. New code should set DBPath.
	OAuth2DBPath        string
	OAuth2Issuer        string   // OAuth2 issuer URL (default: listen address)
	OAuth2ClientID      string   // OAuth2 client ID for SSO (optional)
	OAuth2ClientSecret  string   // OAuth2 client secret for SSO (optional)
	OAuth2AuthURL       string   // OAuth2 authorization URL for SSO (optional)
	OAuth2TokenURL      string   // OAuth2 token URL for SSO (optional)
	OAuth2RedirectURL   string   // OAuth2 redirect URL for SSO (optional)
	OAuth2UserInfoURL   string   // OAuth2 user info endpoint for SSO (optional)
	OAuth2Scopes        []string // OAuth2 scopes to request (default: ["openid", "profile", "email"])
	OAuth2UsernameClaim string   // Claim name for username in token (default: "sub")
	OAuth2GroupsClaim   string   // Claim name for groups in user info (default: "groups")
	// OAuth2AccessTokenLifespan is how long an access token issued by the embedded
	// MCP issuer is valid. Defaults to 1 hour if zero.
	OAuth2AccessTokenLifespan time.Duration
	// OAuth2RefreshTokenLifespan is how long a refresh token issued by the embedded
	// MCP issuer is valid. Defaults to 30 days if zero. Must be >= OAuth2AccessTokenLifespan;
	// otherwise refresh grants will fail before the access token expires (see PelicanPlatform/pelican#3389).
	OAuth2RefreshTokenLifespan time.Duration
	MCPAccessGroup             string // Group required for any MCP access (empty = all authenticated)
	MCPReadGroup               string // Group required for read operations (empty = all have read)
	MCPWriteGroup              string // Group required for write operations (empty = all have write)
	MCPInstructions            string // Server-level instructions provided to all MCP agents (e.g., AP-specific guidance)
	WebUIAdminGroup            string // Group required for Web UI admin pages (empty disables admin UI). Configurable via HTTP_API_WEBUI_ADMIN_GROUP.
	EnableIDP                  bool   // Enable built-in IDP (always enabled in demo mode)
	// IDPDBPath is deprecated; the IDP shares the unified DBPath.
	// Retained as an unused field so existing callers keep compiling
	// during the transition.
	IDPDBPath string //nolint:unused // kept for back-compat; ignored by NewHandler.
	IDPIssuer string // IDP issuer URL (default: listen address)
	// IDPAccessTokenLifespan / IDPRefreshTokenLifespan: see OAuth2*Lifespan above. Zero
	// uses the same defaults (1h / 30d).
	IDPAccessTokenLifespan  time.Duration
	IDPRefreshTokenLifespan time.Duration
	SessionTTL              time.Duration        // HTTP session TTL (default: 24h)
	HTCondorConfig          *config.Config       // HTCondor configuration (optional, used for LOCAL_DIR default)
	PingInterval            time.Duration        // Interval for periodic daemon pings (default: 1 minute, 0 = disabled)
	StreamBufferSize        int                  // Buffer size for streaming queries (default: 100)
	StreamWriteTimeout      time.Duration        // Write timeout for streaming queries (default: 5s)
	Token                   string               // Token for daemon authentication (optional)
	Credd                   htcondor.CreddClient // Optional credd client; defaults to in-memory implementation

	// JupyterWorkDir is where the embedded helper binary (materialized
	// from package jupyterhelperbin) and per-instance scratch artifacts
	// (token files) are staged. Files persist for the lifetime of the
	// job since HTCondor reads transfer_input_files at job-startup time.
	// Defaults to <os.TempDir>/htcondor-api-jupyter.
	JupyterWorkDir string

	// TemplateGlobalPath is an optional YAML file with operator-curated
	// batch-submission templates. Empty disables. Built-in templates
	// always ship.
	TemplateGlobalPath string

	// TemplateUserStoreDBPath is deprecated; the templates store now
	// shares the unified DBPath. Retained so existing callers
	// keep compiling; ignored by NewHandler.
	TemplateUserStoreDBPath string //nolint:unused // kept for back-compat; ignored by NewHandler.
}

HandlerConfig holds handler configuration

type HistoryListResponse added in v0.0.4

type HistoryListResponse struct {
	Ads []*classad.ClassAd `json:"ads"`
}

HistoryListResponse represents a history listing response

type IDPProvider added in v0.0.4

type IDPProvider struct {
	// contains filtered or unexported fields
}

IDPProvider manages OAuth2 operations for the built-in IDP

func NewIDPProvider added in v0.0.4

func NewIDPProvider(opts IDPProviderOptions) (*IDPProvider, error)

NewIDPProvider creates a new IDP provider with SQLite storage. Both AccessTokenLifespan and RefreshTokenLifespan in opts must be > 0; otherwise an error is returned. See OAuth2ProviderOptions for the rationale.

func (*IDPProvider) Close added in v0.0.4

func (p *IDPProvider) Close() error

Close is now a no-op: the IDP provider does not own the underlying *sql.DB anymore. The Handler that opened the unified app DB is responsible for closing it on shutdown.

func (*IDPProvider) GetProvider added in v0.0.4

func (p *IDPProvider) GetProvider() fosite.OAuth2Provider

GetProvider returns the underlying fosite OAuth2Provider

func (*IDPProvider) GetStorage added in v0.0.4

func (p *IDPProvider) GetStorage() *IDPStorage

GetStorage returns the IDP storage

func (*IDPProvider) GetStrategy added in v0.0.4

func (p *IDPProvider) GetStrategy() *compose.CommonStrategy

GetStrategy returns the OAuth2 strategy

func (*IDPProvider) UpdateIssuer added in v0.0.4

func (p *IDPProvider) UpdateIssuer(issuer string)

UpdateIssuer updates the issuer URL in the OAuth2 config

type IDPProviderOptions added in v0.0.11

type IDPProviderOptions struct {
	DB                   *sql.DB
	Issuer               string
	AccessTokenLifespan  time.Duration
	RefreshTokenLifespan time.Duration
	// Sealer envelope-encrypts the IDP's RSA private key + HMAC
	// GlobalSecret. See OAuth2ProviderOptions.Sealer.
	Sealer *seal.Sealer
}

IDPProviderOptions configures lifespans and other tunables for the IDP provider. DB is the unified application database (see appdb); the provider does not own its lifecycle.

type IDPStorage added in v0.0.4

type IDPStorage struct {
	// contains filtered or unexported fields
}

IDPStorage implements fosite storage interfaces using the unified application database. The IDP's tables (idp_*) live alongside the OAuth2/MCP tables in the same SQLite file managed by appdb.

See OAuth2Storage for the sealer field's role.

func NewIDPStorage added in v0.0.4

func NewIDPStorage(db *sql.DB) *IDPStorage

NewIDPStorage wraps an already-opened, already-migrated DB. Schema is owned by the appdb migrations; this struct only holds the query helpers. The caller retains DB ownership.

func (*IDPStorage) AuthenticateUser added in v0.0.4

func (s *IDPStorage) AuthenticateUser(ctx context.Context, username, password string) error

AuthenticateUser verifies username and password

func (*IDPStorage) ClientAssertionJWTValid added in v0.0.4

func (s *IDPStorage) ClientAssertionJWTValid(ctx context.Context, jti string) error

ClientAssertionJWTValid implements fosite.ClientAssertionJWTValid interface

func (*IDPStorage) CreateAccessTokenSession added in v0.0.4

func (s *IDPStorage) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) error

CreateAccessTokenSession stores an access token session

func (*IDPStorage) CreateAuthorizeCodeSession added in v0.0.4

func (s *IDPStorage) CreateAuthorizeCodeSession(ctx context.Context, signature string, request fosite.Requester) error

CreateAuthorizeCodeSession stores an authorization code session

func (*IDPStorage) CreateClient added in v0.0.4

func (s *IDPStorage) CreateClient(ctx context.Context, client *fosite.DefaultClient) error

CreateClient creates a new OAuth2 client

func (*IDPStorage) CreateOpenIDConnectSession added in v0.0.4

func (s *IDPStorage) CreateOpenIDConnectSession(ctx context.Context, signature string, requester fosite.Requester) error

CreateOpenIDConnectSession implements openid.OpenIDConnectRequestStorage interface

func (*IDPStorage) CreatePKCERequestSession added in v0.0.4

func (s *IDPStorage) CreatePKCERequestSession(ctx context.Context, signature string, request fosite.Requester) error

CreatePKCERequestSession stores a PKCE request session

func (*IDPStorage) CreateRefreshTokenSession added in v0.0.4

func (s *IDPStorage) CreateRefreshTokenSession(ctx context.Context, signature string, request fosite.Requester) error

CreateRefreshTokenSession stores a refresh token session

func (*IDPStorage) CreateSession added in v0.0.4

func (s *IDPStorage) CreateSession(ctx context.Context, username string) (string, error)

CreateSession creates a new session for the given username

func (*IDPStorage) CreateUser added in v0.0.4

func (s *IDPStorage) CreateUser(ctx context.Context, username, password, state string) error

CreateUser creates a new user with hashed password and specified state

func (*IDPStorage) DeleteAccessTokenSession added in v0.0.4

func (s *IDPStorage) DeleteAccessTokenSession(ctx context.Context, signature string) error

DeleteAccessTokenSession deletes an access token session

func (*IDPStorage) DeleteOpenIDConnectSession added in v0.0.4

func (s *IDPStorage) DeleteOpenIDConnectSession(ctx context.Context, signature string) error

DeleteOpenIDConnectSession implements openid.OpenIDConnectRequestStorage interface

func (*IDPStorage) DeletePKCERequestSession added in v0.0.4

func (s *IDPStorage) DeletePKCERequestSession(ctx context.Context, signature string) error

DeletePKCERequestSession deletes a PKCE request session

func (*IDPStorage) DeleteRefreshTokenSession added in v0.0.4

func (s *IDPStorage) DeleteRefreshTokenSession(ctx context.Context, signature string) error

DeleteRefreshTokenSession deletes a refresh token session

func (*IDPStorage) DeleteSession added in v0.0.4

func (s *IDPStorage) DeleteSession(ctx context.Context, sessionID string) error

DeleteSession deletes a session

func (*IDPStorage) GetAccessTokenSession added in v0.0.4

func (s *IDPStorage) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)

GetAccessTokenSession retrieves an access token session

func (*IDPStorage) GetAuthorizeCodeSession added in v0.0.4

func (s *IDPStorage) GetAuthorizeCodeSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)

GetAuthorizeCodeSession retrieves an authorization code session

func (*IDPStorage) GetClient added in v0.0.4

func (s *IDPStorage) GetClient(ctx context.Context, clientID string) (fosite.Client, error)

GetClient retrieves a client by ID

func (*IDPStorage) GetOpenIDConnectSession added in v0.0.4

func (s *IDPStorage) GetOpenIDConnectSession(ctx context.Context, signature string, requester fosite.Requester) (fosite.Requester, error)

GetOpenIDConnectSession implements openid.OpenIDConnectRequestStorage interface

func (*IDPStorage) GetPKCERequestSession added in v0.0.4

func (s *IDPStorage) GetPKCERequestSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)

GetPKCERequestSession retrieves a PKCE request session

func (*IDPStorage) GetRefreshTokenSession added in v0.0.4

func (s *IDPStorage) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)

GetRefreshTokenSession retrieves a refresh token session

func (*IDPStorage) GetSession added in v0.0.4

func (s *IDPStorage) GetSession(ctx context.Context, sessionID string) (string, error)

GetSession retrieves the username for a given session ID

func (*IDPStorage) GetUserState added in v0.0.4

func (s *IDPStorage) GetUserState(ctx context.Context, username string) (string, error)

GetUserState retrieves the state of a user

func (*IDPStorage) InvalidateAuthorizeCodeSession added in v0.0.4

func (s *IDPStorage) InvalidateAuthorizeCodeSession(ctx context.Context, signature string) error

InvalidateAuthorizeCodeSession invalidates an authorization code

func (*IDPStorage) LoadHMACSecret added in v0.0.4

func (s *IDPStorage) LoadHMACSecret(ctx context.Context) ([]byte, error)

LoadHMACSecret loads the HMAC secret. See OAuth2Storage.LoadHMACSecret.

func (*IDPStorage) LoadRSAKey added in v0.0.4

func (s *IDPStorage) LoadRSAKey(ctx context.Context) (string, error)

LoadRSAKey loads the RSA private key. See OAuth2Storage.LoadRSAKey.

func (*IDPStorage) RevokeAccessToken added in v0.0.4

func (s *IDPStorage) RevokeAccessToken(ctx context.Context, requestID string) error

RevokeAccessToken revokes an access token

func (*IDPStorage) RevokeRefreshToken added in v0.0.4

func (s *IDPStorage) RevokeRefreshToken(ctx context.Context, requestID string) error

RevokeRefreshToken revokes a refresh token

func (*IDPStorage) RevokeRefreshTokenMaybeGracePeriod added in v0.0.4

func (s *IDPStorage) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestID string, _ string) error

RevokeRefreshTokenMaybeGracePeriod implements fosite.TokenRevocationStorage interface

func (*IDPStorage) SaveHMACSecret added in v0.0.4

func (s *IDPStorage) SaveHMACSecret(ctx context.Context, secret []byte) error

SaveHMACSecret stores the HMAC secret. See OAuth2Storage.SaveHMACSecret.

func (*IDPStorage) SaveRSAKey added in v0.0.4

func (s *IDPStorage) SaveRSAKey(ctx context.Context, privateKeyPEM string) error

SaveRSAKey stores the RSA private key. Mirrors OAuth2Storage's implementation — see SaveRSAKey there for the encryption rationale.

func (*IDPStorage) SetClientAssertionJWT added in v0.0.4

func (s *IDPStorage) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error

SetClientAssertionJWT implements fosite.SetClientAssertionJWT interface

func (*IDPStorage) SetSealer added in v0.0.11

func (s *IDPStorage) SetSealer(sealer *seal.Sealer)

SetSealer enables envelope encryption for the long-lived secret columns (RSA private key, HMAC secret). See OAuth2Storage.SetSealer.

func (*IDPStorage) UserExists added in v0.0.4

func (s *IDPStorage) UserExists(ctx context.Context, username string) (bool, error)

UserExists checks if a user exists

type InteractiveCreateTerminalRequest added in v0.0.11

type InteractiveCreateTerminalRequest struct {
	Cpus     int `json:"cpus,omitempty"`
	MemoryMB int `json:"memory_mb,omitempty"`
	DiskMB   int `json:"disk_mb,omitempty"`

	// GPU fields. Mirrored verbatim into request_gpus and the
	// gpus_minimum_* / cuda_version / require_gpus submit lines.
	// Gpus == 0 disables the entire GPU section in the submit file.
	Gpus                  int    `json:"gpus,omitempty"`
	GpusMinimumCapability string `json:"gpus_minimum_capability,omitempty"`
	GpusMinimumMemory     int    `json:"gpus_minimum_memory,omitempty"`
	GpusMinimumRuntime    string `json:"gpus_minimum_runtime,omitempty"`
	CudaVersion           string `json:"cuda_version,omitempty"`
	RequireGpus           string `json:"require_gpus,omitempty"`
}

InteractiveCreateTerminalRequest is the optional JSON body of POST /api/v1/interactive/terminal. All fields are optional; the server fills sensible defaults.

type InteractiveCreateTerminalResponse added in v0.0.11

type InteractiveCreateTerminalResponse struct {
	InstanceID string `json:"instance_id"`
	ClusterID  int    `json:"cluster_id"`
	ProcID     int    `json:"proc_id"`
	JobID      string `json:"job_id"` // "cluster.proc" — convenience for the SPA
	BatchName  string `json:"batch_name"`
}

InteractiveCreateTerminalResponse is the JSON returned on success.

type InteractiveTerminalSummary added in v0.0.11

type InteractiveTerminalSummary struct {
	InstanceID                   string `json:"instance_id"`
	JobID                        string `json:"job_id"`
	ClusterID                    int    `json:"cluster_id"`
	ProcID                       int    `json:"proc_id"`
	BatchName                    string `json:"batch_name"`
	JobStatus                    int    `json:"job_status"`
	JobCurrentStartExecutingDate int64  `json:"job_current_start_executing_date,omitempty"`
	HoldReasonCode               int    `json:"hold_reason_code,omitempty"`
	HoldReason                   string `json:"hold_reason,omitempty"`
	SubmittedAt                  string `json:"submitted_at,omitempty"` // RFC3339 from QDate
}

InteractiveTerminalSummary is the SPA-facing shape of one terminal session. Returned by GET /api/v1/interactive/terminal.

JobCurrentStartExecutingDate is the schedd's "executable actually started running" timestamp; combined with JobStatus the SPA's shared status module distinguishes "queued" from "transferring input" from "executing".

type JobActionFunc added in v0.0.3

type JobActionFunc func(ctx context.Context, constraint, reason string) (*htcondor.JobActionResults, error)

JobActionFunc is a function that performs a job action (hold, release, etc.)

type JobEditRequest added in v0.0.4

type JobEditRequest struct {
	Attributes map[string]interface{} `json:"attributes"` // Attributes to update
}

JobEditRequest represents a job edit request

type JobListResponse

type JobListResponse struct {
	Jobs []*classad.ClassAd `json:"jobs"`
}

JobListResponse represents a job listing response

type JobLogResponse added in v0.0.11

type JobLogResponse struct {
	JobID     string          `json:"jobId"`
	Filename  string          `json:"filename"`
	Truncated bool            `json:"truncated"`
	Events    []userlog.Event `json:"events"`
}

JobLogResponse is the JSON shape returned by GET /api/v1/jobs/{id}/log. It mirrors the stdout/stderr endpoints — explicit fetch, no streaming.

type JobSubmitRequest

type JobSubmitRequest struct {
	SubmitFile string `json:"submit_file"` // Submit file content
}

JobSubmitRequest represents a job submission request

type JobSubmitResponse

type JobSubmitResponse struct {
	ClusterID int      `json:"cluster_id"`
	JobIDs    []string `json:"job_ids"` // Array of "cluster.proc" strings
}

JobSubmitResponse represents a job submission response

type JupyterCreateRequest added in v0.0.11

type JupyterCreateRequest struct {
	// Image is the Docker image to launch. Default
	// quay.io/jupyter/scipy-notebook:latest.
	Image string `json:"image"`
	// Cpus is the requested core count. Default 2.
	Cpus int `json:"cpus"`
	// MemoryMB is the requested RAM in mebibytes. Default 4096.
	MemoryMB int `json:"memory_mb"`
	// DiskMB is the requested scratch disk in mebibytes. Default 4096.
	DiskMB int `json:"disk_mb"`

	// GPU fields. Mirrored verbatim into request_gpus and the
	// gpus_minimum_* / cuda_version / require_gpus submit lines.
	// Gpus == 0 disables the entire GPU section in the submit file.
	Gpus                  int    `json:"gpus,omitempty"`
	GpusMinimumCapability string `json:"gpus_minimum_capability,omitempty"`
	GpusMinimumMemory     int    `json:"gpus_minimum_memory,omitempty"`
	GpusMinimumRuntime    string `json:"gpus_minimum_runtime,omitempty"`
	CudaVersion           string `json:"cuda_version,omitempty"`
	RequireGpus           string `json:"require_gpus,omitempty"`
}

JupyterCreateRequest is the optional JSON body of POST /jupyter/instances. All fields have sensible defaults so a bare {} is a valid request.

type JupyterCreateResponse added in v0.0.11

type JupyterCreateResponse struct {
	InstanceID string `json:"instance_id"`
	ClusterID  string `json:"cluster_id"`
	// ProxyPath is where the browser should eventually point its iframe
	// (only useful once the helper has connected back; the SSE stream
	// from /events tells you when).
	ProxyPath string `json:"proxy_path"`
}

JupyterCreateResponse is the JSON returned by POST /jupyter/instances.

type JupyterInstanceSummary added in v0.0.11

type JupyterInstanceSummary struct {
	InstanceID                   string `json:"instance_id"`
	ClusterID                    string `json:"cluster_id,omitempty"`
	Image                        string `json:"image,omitempty"`
	Owner                        string `json:"owner"`
	CreatedAt                    string `json:"created_at"`
	Connected                    bool   `json:"connected"` // helper has dialed back
	ProxyPath                    string `json:"proxy_path"`
	EventsPath                   string `json:"events_path"`
	JobStatus                    int    `json:"job_status,omitempty"`
	JobCurrentStartExecutingDate int64  `json:"job_current_start_executing_date,omitempty"`
	HoldReasonCode               int    `json:"hold_reason_code,omitempty"`
	HoldReason                   string `json:"hold_reason,omitempty"`
}

JupyterInstanceSummary is the SPA-facing shape returned by both GET /api/v1/jupyter/instances (list) and GET /api/v1/jupyter/instances/{id} (single). The proxy_path is what the iframe should mount; the events_path drives the SSE stream.

The job_* fields are populated from a single bulk schedd query (handleJupyterListInstances) so the list view can run the same status-interpretation logic the detail page uses, without a round-trip per row. Empty when the schedd query failed or the cluster is gone — the SPA falls back to a "loading"/"connected only" view in that case.

type LoginRateLimiter added in v0.0.4

type LoginRateLimiter struct {
	// contains filtered or unexported fields
}

LoginRateLimiter manages rate limiting for login attempts per IP address

func NewLoginRateLimiter added in v0.0.4

func NewLoginRateLimiter(r rate.Limit, b int) *LoginRateLimiter

NewLoginRateLimiter creates a new login rate limiter rate: maximum requests per second per IP burst: maximum burst size per IP

func (*LoginRateLimiter) Allow added in v0.0.4

func (l *LoginRateLimiter) Allow(ip string) bool

Allow checks if a login attempt from the given IP is allowed

type OAuth2Provider added in v0.0.3

type OAuth2Provider struct {
	// contains filtered or unexported fields
}

OAuth2Provider manages OAuth2 operations

func NewOAuth2Provider added in v0.0.3

func NewOAuth2Provider(opts OAuth2ProviderOptions) (*OAuth2Provider, error)

NewOAuth2Provider creates a new OAuth2 provider with SQLite storage. Both AccessTokenLifespan and RefreshTokenLifespan in opts must be > 0; otherwise an error is returned. This is intentional: silent fallback to fosite's defaults (1h access, 30d refresh) has bitten downstream projects when callers forget to pass them through, so callers must opt in explicitly.

func (*OAuth2Provider) Close added in v0.0.3

func (p *OAuth2Provider) Close() error

Close is now a no-op: the OAuth2 provider does not own the underlying *sql.DB anymore. The Handler that opened the unified app DB is responsible for closing it on shutdown. Method retained so callers that defer p.Close() during refactors don't break.

func (*OAuth2Provider) GetProvider added in v0.0.3

func (p *OAuth2Provider) GetProvider() fosite.OAuth2Provider

GetProvider returns the underlying fosite OAuth2Provider

func (*OAuth2Provider) GetStorage added in v0.0.3

func (p *OAuth2Provider) GetStorage() *OAuth2Storage

GetStorage returns the OAuth2 storage

func (*OAuth2Provider) GetStrategy added in v0.0.4

func (p *OAuth2Provider) GetStrategy() *compose.CommonStrategy

GetStrategy returns the OAuth2 strategy

func (*OAuth2Provider) IntrospectToken added in v0.0.4

func (p *OAuth2Provider) IntrospectToken(ctx context.Context, token string) (fosite.Session, error)

IntrospectToken validates an access token and returns the session

func (*OAuth2Provider) UpdateIssuer added in v0.0.4

func (p *OAuth2Provider) UpdateIssuer(issuer string)

UpdateIssuer updates the issuer URL in the configuration This is useful when using port 0 and getting the actual port after server start

type OAuth2ProviderOptions added in v0.0.11

type OAuth2ProviderOptions struct {
	DB                   *sql.DB
	Issuer               string
	AccessTokenLifespan  time.Duration
	RefreshTokenLifespan time.Duration
	// Sealer envelope-encrypts long-lived secrets in the DB (the
	// issuer's RSA private key, fosite's HMAC GlobalSecret). When
	// non-nil, the storage adapter pulls/pushes ciphertext + wrapped
	// DEK on the corresponding load/save calls. Nil = plaintext.
	Sealer *seal.Sealer
}

OAuth2ProviderOptions configures lifespans and other tunables for the OAuth2 provider. Lifespans must be > 0; callers are expected to validate or default before constructing. DB is the unified application database (see appdb); the provider does not own its lifecycle.

type OAuth2StateEntry added in v0.0.3

type OAuth2StateEntry struct {
	AuthorizeRequest fosite.AuthorizeRequester
	Timestamp        time.Time
	OriginalURL      string   // Original URL to redirect back to after authentication
	Username         string   // Authenticated username for consent flow
	Groups           []string // User groups for scope filtering in consent flow
}

OAuth2StateEntry represents a stored OAuth2 authorization state

type OAuth2StateStore added in v0.0.3

type OAuth2StateStore struct {
	// contains filtered or unexported fields
}

OAuth2StateStore manages OAuth2 state parameters for the authorization flow

func NewOAuth2StateStore added in v0.0.3

func NewOAuth2StateStore() *OAuth2StateStore

NewOAuth2StateStore creates a new OAuth2 state store Call Start() to begin the cleanup goroutine

func (*OAuth2StateStore) GenerateState added in v0.0.3

func (s *OAuth2StateStore) GenerateState() (string, error)

GenerateState generates a secure random state parameter

func (*OAuth2StateStore) Get added in v0.0.3

Get retrieves and removes an authorize request for the given state

func (*OAuth2StateStore) GetWithURL added in v0.0.4

func (s *OAuth2StateStore) GetWithURL(state string) (fosite.AuthorizeRequester, string, bool)

GetWithURL retrieves and removes an authorize request for the given state along with the original URL

func (*OAuth2StateStore) GetWithUsername added in v0.0.4

func (s *OAuth2StateStore) GetWithUsername(state string) (fosite.AuthorizeRequester, string, []string, bool)

GetWithUsername retrieves an authorize request for the given state along with username and groups (without removing)

func (*OAuth2StateStore) Remove added in v0.0.4

func (s *OAuth2StateStore) Remove(state string)

Remove removes an entry for the given state

func (*OAuth2StateStore) Start added in v0.0.7

func (s *OAuth2StateStore) Start(ctx context.Context)

Start begins the cleanup goroutine

func (*OAuth2StateStore) Store added in v0.0.3

func (s *OAuth2StateStore) Store(state string, ar fosite.AuthorizeRequester)

Store stores an authorize request with the given state

func (*OAuth2StateStore) StoreWithURL added in v0.0.4

func (s *OAuth2StateStore) StoreWithURL(state string, ar fosite.AuthorizeRequester, originalURL string)

StoreWithURL stores an authorize request with the given state and original URL

func (*OAuth2StateStore) StoreWithUsername added in v0.0.4

func (s *OAuth2StateStore) StoreWithUsername(state string, ar fosite.AuthorizeRequester, originalURL, username string, groups ...[]string)

StoreWithUsername stores an authorize request with the given state, original URL, and username

func (*OAuth2StateStore) Wait added in v0.0.7

func (s *OAuth2StateStore) Wait()

Wait waits for the cleanup goroutine to finish

type OAuth2Storage added in v0.0.3

type OAuth2Storage struct {
	// contains filtered or unexported fields
}

OAuth2Storage implements fosite storage interfaces using the unified application database. The schema is owned by appdb's migrations — this struct is purely a thin set of query helpers around an already- migrated *sql.DB.

The optional `sealer` field is the application's envelope-encryption gate. When non-nil, SaveRSAKey / SaveHMACSecret store ciphertext + wrapped DEK; LoadRSAKey / LoadHMACSecret transparently decrypt rows whose DEK column is populated. When nil, the storage falls back to the pre-encryption plaintext behavior — back-compat for deployments that haven't configured a KEK yet.

func NewOAuth2Storage added in v0.0.3

func NewOAuth2Storage(db *sql.DB) *OAuth2Storage

NewOAuth2Storage wraps an already-opened DB in the OAuth2 storage helpers. Schema creation is no longer this struct's responsibility — see httpserver/appdb. The caller retains ownership of the DB (don't call Close() here on shutdown).

The returned storage starts in plaintext mode; call SetSealer if a KEK has been loaded.

func (*OAuth2Storage) ApproveDeviceCodeSession added in v0.0.4

func (s *OAuth2Storage) ApproveDeviceCodeSession(ctx context.Context, userCode string, subject string, session fosite.Session) error

ApproveDeviceCodeSession approves a device code (user authorized the device)

func (*OAuth2Storage) ClientAssertionJWTValid added in v0.0.3

func (s *OAuth2Storage) ClientAssertionJWTValid(ctx context.Context, jti string) error

ClientAssertionJWTValid implements fosite.ClientAssertionJWTValid interface This checks if a JWT ID (JTI) has already been used to prevent replay attacks

func (*OAuth2Storage) CreateAccessTokenSession added in v0.0.3

func (s *OAuth2Storage) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) error

CreateAccessTokenSession stores an access token session

func (*OAuth2Storage) CreateAuthorizeCodeSession added in v0.0.3

func (s *OAuth2Storage) CreateAuthorizeCodeSession(ctx context.Context, signature string, request fosite.Requester) error

CreateAuthorizeCodeSession stores an authorization code session

func (*OAuth2Storage) CreateClient added in v0.0.3

func (s *OAuth2Storage) CreateClient(ctx context.Context, client *fosite.DefaultClient) error

CreateClient creates a new OAuth2 client

func (*OAuth2Storage) CreateDeviceCodeSession added in v0.0.4

func (s *OAuth2Storage) CreateDeviceCodeSession(ctx context.Context, deviceCode string, userCode string, request fosite.Requester, expiresAt time.Time) error

CreateDeviceCodeSession creates a new device code session

func (*OAuth2Storage) CreateOpenIDConnectSession added in v0.0.3

func (s *OAuth2Storage) CreateOpenIDConnectSession(ctx context.Context, signature string, requester fosite.Requester) error

CreateOpenIDConnectSession implements openid.OpenIDConnectRequestStorage interface

func (*OAuth2Storage) CreatePKCERequestSession added in v0.0.4

func (s *OAuth2Storage) CreatePKCERequestSession(ctx context.Context, signature string, request fosite.Requester) error

CreatePKCERequestSession stores a PKCE request session

func (*OAuth2Storage) CreateRefreshTokenSession added in v0.0.3

func (s *OAuth2Storage) CreateRefreshTokenSession(ctx context.Context, signature string, request fosite.Requester) error

CreateRefreshTokenSession stores a refresh token session

func (*OAuth2Storage) DeleteAccessTokenSession added in v0.0.3

func (s *OAuth2Storage) DeleteAccessTokenSession(ctx context.Context, signature string) error

DeleteAccessTokenSession deletes an access token session

func (*OAuth2Storage) DeleteOpenIDConnectSession added in v0.0.3

func (s *OAuth2Storage) DeleteOpenIDConnectSession(ctx context.Context, signature string) error

DeleteOpenIDConnectSession implements openid.OpenIDConnectRequestStorage interface

func (*OAuth2Storage) DeletePKCERequestSession added in v0.0.4

func (s *OAuth2Storage) DeletePKCERequestSession(ctx context.Context, signature string) error

DeletePKCERequestSession deletes a PKCE request session

func (*OAuth2Storage) DeleteRefreshTokenSession added in v0.0.3

func (s *OAuth2Storage) DeleteRefreshTokenSession(ctx context.Context, signature string) error

DeleteRefreshTokenSession deletes a refresh token session

func (*OAuth2Storage) DenyDeviceCodeSession added in v0.0.4

func (s *OAuth2Storage) DenyDeviceCodeSession(ctx context.Context, userCode string) error

DenyDeviceCodeSession denies a device code (user rejected the device)

func (*OAuth2Storage) GetAccessTokenSession added in v0.0.3

func (s *OAuth2Storage) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)

GetAccessTokenSession retrieves an access token session

func (*OAuth2Storage) GetAuthorizeCodeSession added in v0.0.3

func (s *OAuth2Storage) GetAuthorizeCodeSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)

GetAuthorizeCodeSession retrieves an authorization code session

func (*OAuth2Storage) GetClient added in v0.0.3

func (s *OAuth2Storage) GetClient(ctx context.Context, clientID string) (fosite.Client, error)

GetClient retrieves a client by ID

func (*OAuth2Storage) GetDB added in v0.0.4

func (s *OAuth2Storage) GetDB() *sql.DB

GetDB returns the underlying database connection. Kept on the struct because tests and the SessionStore wiring still reach for it.

func (*OAuth2Storage) GetDeviceCodeSession added in v0.0.4

func (s *OAuth2Storage) GetDeviceCodeSession(ctx context.Context, deviceCode string, session fosite.Session) (fosite.Requester, error)

GetDeviceCodeSession retrieves a device code session by device code

func (*OAuth2Storage) GetDeviceCodeSessionByUserCode added in v0.0.4

func (s *OAuth2Storage) GetDeviceCodeSessionByUserCode(ctx context.Context, userCode string) (string, fosite.Requester, error)

GetDeviceCodeSessionByUserCode retrieves a device code session by user code

func (*OAuth2Storage) GetOpenIDConnectSession added in v0.0.3

func (s *OAuth2Storage) GetOpenIDConnectSession(ctx context.Context, signature string, requester fosite.Requester) (fosite.Requester, error)

GetOpenIDConnectSession implements openid.OpenIDConnectRequestStorage interface

func (*OAuth2Storage) GetPKCERequestSession added in v0.0.4

func (s *OAuth2Storage) GetPKCERequestSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)

GetPKCERequestSession retrieves a PKCE request session

func (*OAuth2Storage) GetRefreshTokenSession added in v0.0.3

func (s *OAuth2Storage) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)

GetRefreshTokenSession retrieves a refresh token session

func (*OAuth2Storage) InvalidateAuthorizeCodeSession added in v0.0.3

func (s *OAuth2Storage) InvalidateAuthorizeCodeSession(ctx context.Context, signature string) error

InvalidateAuthorizeCodeSession invalidates an authorization code

func (*OAuth2Storage) InvalidateDeviceCodeSession added in v0.0.4

func (s *OAuth2Storage) InvalidateDeviceCodeSession(ctx context.Context, deviceCode string) error

InvalidateDeviceCodeSession invalidates a device code after it's been used

func (*OAuth2Storage) LoadHMACSecret added in v0.0.3

func (s *OAuth2Storage) LoadHMACSecret(ctx context.Context) ([]byte, error)

LoadHMACSecret loads the HMAC secret. See LoadRSAKey for the encryption-vs-plaintext branching.

func (*OAuth2Storage) LoadRSAKey added in v0.0.3

func (s *OAuth2Storage) LoadRSAKey(ctx context.Context) (string, error)

LoadRSAKey loads the RSA private key. Falls back to plaintext when the row's DEK column is NULL — that's the pre-encryption format and the format used when no KEK is configured. When a DEK is present but no sealer is configured (KEK was removed without rotating data), returns an explicit error rather than handing back ciphertext or silently regenerating the key.

func (*OAuth2Storage) RevokeAccessToken added in v0.0.3

func (s *OAuth2Storage) RevokeAccessToken(ctx context.Context, requestID string) error

RevokeAccessToken revokes an access token

func (*OAuth2Storage) RevokeRefreshToken added in v0.0.3

func (s *OAuth2Storage) RevokeRefreshToken(ctx context.Context, requestID string) error

RevokeRefreshToken revokes a refresh token

func (*OAuth2Storage) RevokeRefreshTokenMaybeGracePeriod added in v0.0.3

func (s *OAuth2Storage) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestID string, _ string) error

RevokeRefreshTokenMaybeGracePeriod implements fosite.TokenRevocationStorage interface This handles refresh token revocation. The signature parameter allows for grace period implementation but for simplicity we immediately revoke the token by request ID

func (*OAuth2Storage) SaveHMACSecret added in v0.0.3

func (s *OAuth2Storage) SaveHMACSecret(ctx context.Context, secret []byte) error

SaveHMACSecret stores the HMAC secret. See SaveRSAKey for the encryption-vs-plaintext branching.

func (*OAuth2Storage) SaveRSAKey added in v0.0.3

func (s *OAuth2Storage) SaveRSAKey(ctx context.Context, privateKeyPEM string) error

SaveRSAKey stores the RSA private key. When a sealer is set the PEM bytes are encrypted under a fresh per-row DEK (itself wrapped by the DB-instance KEK); when no sealer is configured the PEM is written verbatim — same on-disk shape as the pre-KEK schema, kept for back-compat with deployments that haven't enabled encryption.

func (*OAuth2Storage) SetClientAssertionJWT added in v0.0.3

func (s *OAuth2Storage) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error

SetClientAssertionJWT implements fosite.SetClientAssertionJWT interface This stores the JTI (JWT ID) with expiration to prevent replay attacks

func (*OAuth2Storage) SetSealer added in v0.0.11

func (s *OAuth2Storage) SetSealer(sealer *seal.Sealer)

SetSealer enables envelope encryption for the long-lived secret columns (RSA private key, HMAC secret). Calling with nil restores plaintext mode. Callers should set this once at startup, before SaveRSAKey / SaveHMACSecret have a chance to fire — the startup-backfill path in NewHandler does exactly that.

func (*OAuth2Storage) UpdateDeviceCodePolling added in v0.0.4

func (s *OAuth2Storage) UpdateDeviceCodePolling(ctx context.Context, deviceCode string) error

UpdateDeviceCodePolling updates the last polled timestamp for rate limiting

type PingResponse added in v0.0.4

type PingResponse struct {
	Daemon         string `json:"daemon"`               // "collector" or "schedd"
	AuthMethod     string `json:"auth_method"`          // Authentication method used
	User           string `json:"user"`                 // Authenticated username
	SessionID      string `json:"session_id"`           // Session identifier
	ValidCommands  string `json:"valid_commands"`       // Commands authorized
	Encryption     bool   `json:"encryption"`           // Whether encryption is enabled
	Authentication bool   `json:"authentication"`       // Whether authentication is enabled
	Authorized     bool   `json:"authorized,omitempty"` // Whether authorized for requested permission (if permission checked)
	Permission     string `json:"permission,omitempty"` // Permission level checked (if any)
}

PingResponse represents a ping response for a daemon

type Server

type Server struct {
	*Handler // Embedded handler for business logic
	// contains filtered or unexported fields
}

Server represents the HTTP API server

func NewServer

func NewServer(cfg Config) (*Server, error)

NewServer creates a new HTTP API server

func (*Server) GetAddr added in v0.0.3

func (s *Server) GetAddr() string

GetAddr returns the actual listening address of the server. Returns empty string if the server hasn't started yet.

func (*Server) ServeListener added in v0.0.11

func (s *Server) ServeListener(ln net.Listener, scheme string) error

ServeListener runs the API server on a caller-supplied net.Listener. scheme controls which protocol the request URLs are advertised under ("http" or "https") — use "https" if the caller has configured httpServer.TLSConfig, "http" otherwise.

This is the entry point used when condor_master spawns us as a managed daemon and we accept forwarded connections from condor_shared_port via a sharedport.Listener instead of binding our own TCP port. The handler bootstrap (issuer URL, OAuth2 setup) is the same as Start/StartTLS; the only difference is the kind of listener we hand to http.Server.Serve.

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the HTTP server

func (*Server) Start

func (s *Server) Start() error

Start starts the HTTP server

func (*Server) StartTLS

func (s *Server) StartTLS(certFile, keyFile string) error

StartTLS starts the HTTPS server with TLS

type SessionData added in v0.0.4

type SessionData struct {
	Username  string    // Authenticated username
	Groups    []string  // User groups from IDP (for scope filtering)
	CreatedAt time.Time // When the session was created
	ExpiresAt time.Time // When the session expires
}

SessionData represents the data stored in a session.

Note: this struct used to carry a Token field that was reserved for per-session HTCondor token storage. The column was never written to and the field is gone — the schema migration in 0002_envelope_encryption.sql drops `http_sessions.token` to remove the unused secret-shaped column. Per-user tokens, when needed, flow through the OAuth2 / IDP storage tables instead.

type SessionStore added in v0.0.4

type SessionStore struct {
	// contains filtered or unexported fields
}

SessionStore manages HTTP sessions with SQLite persistence

func NewSessionStore added in v0.0.4

func NewSessionStore(db *sql.DB, ttl time.Duration) (*SessionStore, error)

NewSessionStore creates a new session store with database persistence The db parameter should be the same database connection used by OAuth2Storage

func (*SessionStore) Cleanup added in v0.0.4

func (s *SessionStore) Cleanup()

Cleanup removes expired sessions

func (*SessionStore) Create added in v0.0.4

func (s *SessionStore) Create(username string, groups ...[]string) (string, *SessionData, error)

Create creates a new session for the given username and groups

func (*SessionStore) Delete added in v0.0.4

func (s *SessionStore) Delete(sessionID string)

Delete removes a session

func (*SessionStore) Get added in v0.0.4

func (s *SessionStore) Get(sessionID string) *SessionData

Get retrieves a session by ID Returns nil if session doesn't exist or has expired

func (*SessionStore) Size added in v0.0.4

func (s *SessionStore) Size() int

Size returns the number of active sessions

type ShareOutputRequest added in v0.0.11

type ShareOutputRequest struct {
	TTLSeconds int `json:"ttl_seconds,omitempty"`
}

ShareOutputRequest is the body for POST /api/v1/jobs/{id}/output/share.

type ShareOutputResponse added in v0.0.11

type ShareOutputResponse struct {
	URL        string    `json:"url"`
	ExpiresAt  time.Time `json:"expires_at"`
	TTLSeconds int       `json:"ttl_seconds"`
	Owner      string    `json:"owner"`
}

ShareOutputResponse is what the SPA gets back. Owner is echoed for UX so the share preview can label the URL with "downloads as <owner>".

type TokenCache added in v0.0.3

type TokenCache struct {
	// contains filtered or unexported fields
}

TokenCache manages validated tokens and their associated session caches

func NewTokenCache added in v0.0.3

func NewTokenCache() *TokenCache

NewTokenCache creates a new token cache

func (*TokenCache) Add added in v0.0.3

func (tc *TokenCache) Add(token string) (*TokenCacheEntry, error)

Add adds a validated token to the cache with a session cache If the token is already in the cache, returns the existing entry Automatically schedules cleanup when the token expires

func (*TokenCache) AddValidated added in v0.0.4

func (tc *TokenCache) AddValidated(token, username string, expiration time.Time) (*TokenCacheEntry, error)

AddValidated adds a pre-validated token (e.g. opaque token) to the cache

func (*TokenCache) Get added in v0.0.3

func (tc *TokenCache) Get(token string) (*TokenCacheEntry, bool)

Get retrieves a token cache entry if it exists and is not expired

func (*TokenCache) Remove added in v0.0.3

func (tc *TokenCache) Remove(token string)

Remove removes a token from the cache and cancels its cleanup timer

func (*TokenCache) Size added in v0.0.3

func (tc *TokenCache) Size() int

Size returns the number of cached tokens

type TokenCacheEntry added in v0.0.3

type TokenCacheEntry struct {
	Token        string
	Username     string // Username extracted from JWT (for rate limiting)
	Expiration   time.Time
	SessionCache *security.SessionCache
	// contains filtered or unexported fields
}

TokenCacheEntry represents a cached token with its expiration and associated session cache

type UserInfo added in v0.0.3

type UserInfo struct {
	Subject string                 `json:"sub"`
	Email   string                 `json:"email"`
	Name    string                 `json:"name"`
	Groups  interface{}            `json:"groups"` // Can be []string or string
	Claims  map[string]interface{} // Additional claims
}

UserInfo represents user information from the IDP

type VersionResponse added in v0.0.11

type VersionResponse struct {
	Version string `json:"version"`
	Commit  string `json:"commit"`
}

VersionResponse represents a build-info response.

type WhoAmIResponse added in v0.0.4

type WhoAmIResponse struct {
	Authenticated bool   `json:"authenticated"`
	User          string `json:"user,omitempty"` // Omit if not authenticated
}

WhoAmIResponse represents a whoami response

Directories

Path Synopsis
Package appdb owns the single SQLite database the HTTP API server uses for OAuth2/MCP storage, the embedded IDP, browser sessions, and user-saved batch-submission templates.
Package appdb owns the single SQLite database the HTTP API server uses for OAuth2/MCP storage, the embedded IDP, browser sessions, and user-saved batch-submission templates.
seal
Package seal provides envelope encryption for sensitive columns in the unified application database.
Package seal provides envelope encryption for sensitive columns in the unified application database.
Package jupyterhelperbin without the embed_jupyter_helper tag is a stub that reports "not embedded" — this is the default for `go build ./...` and for dev workflows that don't want a long Makefile dance every time the api binary is rebuilt.
Package jupyterhelperbin without the embed_jupyter_helper tag is a stub that reports "not embedded" — this is the default for `go build ./...` and for dev workflows that don't want a long Makefile dance every time the api binary is rebuilt.
Package webui provides the embedded Next.js static export.
Package webui provides the embedded Next.js static export.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL