Commit Graph

61 Commits

Author SHA1 Message Date
Ejiro Asiuwhu
a26a68225e refactor: extract _get_actual_status helper to deduplicate header parsing
The x-stack-actual-status header was parsed with identical try/except
blocks in 4 places. Extracted into a single _get_actual_status() static
method on BaseAPIClient, used by _parse_response, _get_retry_delay,
and both sync/async request() loops.
2026-03-26 11:43:38 +01:00
Ejiro Asiuwhu
573fe23ea8 fix: treat missing access_token in refresh response as failure
Return (False, None) instead of (True, None) when the OAuth token
endpoint returns 200 but the response has no access_token field.
Also moved Awaitable/Callable imports behind TYPE_CHECKING guard
since they are only used for type annotations.
2026-03-26 11:28:50 +01:00
Ejiro Asiuwhu
94293c14bd fix: raise RateLimitError when 429 retries are exhausted
RateLimitError was defined and exported but could never be raised.
When all retry attempts are exhausted on a 429 response, _parse_response
now raises RateLimitError instead of a generic StackAuthError. Callers
writing `except RateLimitError` will now correctly catch rate limit
failures.
2026-03-26 11:19:01 +01:00
Ejiro Asiuwhu
a6ac1889e1 refactor: use modern Python 3.10+ imports in token store module
Move Awaitable and Callable imports to collections.abc per PEP 585.
Replace Union type alias with pipe syntax for TokenStoreInit.
2026-03-26 11:14:43 +01:00
Ejiro Asiuwhu
9aeed6b366 fix: map TEAM_MEMBERSHIP_NOT_FOUND to NotFoundError
Backend returns HTTP 404 for this error code. Was incorrectly mapped
to ConflictError. Now matches other *_NOT_FOUND codes like
TEAM_NOT_FOUND, USER_NOT_FOUND, and API_KEY_NOT_FOUND.
2026-03-26 10:53:37 +01:00
Ejiro Asiuwhu
6aaa4b3b4b fix: guard against non-dict JSON in token refresh response
resp.json() could return non-dict values (null, string, list) which
would cause AttributeError on data.get("access_token"). Now checks
isinstance(data, dict) first, returning (False, None) for malformed
responses. Applied to both sync and async refresh helpers.
2026-03-26 10:52:22 +01:00
Ejiro Asiuwhu
b44fabc623 fix: validate mutually exclusive product_id/product in grant_product
The spec requires exactly one of product_id or product (inline definition).
Added validation to both sync and async grant_product methods to raise
ValueError if both or neither are provided, matching the existing
_resolve_customer_path pattern for customer identifiers.
2026-03-26 10:36:55 +01:00
Ejiro Asiuwhu
f7786c3a80 docs: add missing docstrings to reach 80% coverage threshold 2026-03-26 10:28:07 +01:00
Ejiro Asiuwhu
e9105b3f68 fix: harden response parsing and add missing None guards
- Guard int() on x-stack-actual-status header against malformed values
  in _parse_response and both retry loops (ValueError falls back to
  response.status_code)
- Validate response.json() returns dict before calling .get() on
  known-error body (non-dict JSON falls back to empty dict)
- Add None guard to get_email_delivery_stats before model_validate
  (matches pattern used by get_user, get_team, get_item)
2026-03-26 10:21:42 +01:00
Ejiro Asiuwhu
131bb89d6e docs: clarify why 429 retries apply to all HTTP methods
A 429 guarantees the server did not process the request, so retrying
is safe regardless of idempotency. This differs from network error
retries which are restricted to idempotent methods. Added inline
comments to both sync and async client request loops.
2026-03-26 09:48:12 +01:00
Ejiro Asiuwhu
cac86efd17 fix: add None guard before model_validate in get_item methods
Both sync and async get_item() were missing the None check that other
getters (get_user, get_team) already have. An empty response body would
surface as a pydantic ValidationError instead of a meaningful NotFoundError.
2026-03-26 09:44:19 +01:00
Ejiro Asiuwhu
37118e0871 fix: skip aud/iss verification when not provided and narrow data vault error handling
- PyJWT defaults verify_aud and verify_iss to True, which rejects tokens
  with iss/aud claims when no expected value is passed. Now explicitly
  disables these checks when audience/issuer are not provided.
- Data vault get/delete now only suppress key-not-found errors
  (DATA_VAULT_STORE_HASHED_KEY_DOES_NOT_EXIST), letting store-not-found
  errors (DATA_VAULT_STORE_DOES_NOT_EXIST) propagate as they should.
2026-03-26 09:27:21 +01:00
Ejiro Asiuwhu
5cbad57027 fix: catch NotFoundError in data vault delete to match spec contract
The spec states "No error if key doesn't exist" but delete() was letting
404 responses bubble up as NotFoundError. Now mirrors the get() pattern
by catching NotFoundError silently, for both sync and async variants.
2026-03-26 09:06:38 +01:00
Ejiro Asiuwhu
d0550079f7 fix: address PR review findings from bot analysis
- Fix incorrect docstring method names in get_item (set_quantity ->
  increase_quantity, decrease_quantity, try_decrease_quantity)
- Add asyncio.Lock to AsyncJWKSFetcher._fetch_jwks to deduplicate
  concurrent JWKS fetches on cold/expired cache
- Add threading.Lock to SyncJWKSFetcher._fetch_jwks for same reason
- Add threading.Lock to _get_or_create_memory_store to prevent
  race condition in token store registry
- Fix README clone URL to point to upstream repo
- Fix E2E test SyncJWKSFetcher constructor to use correct signature
  (jwks_url + http_client instead of project_id + base_url)
2026-03-26 09:01:06 +01:00
Ejiro Asiuwhu
9ddc8aa818 docs: add comprehensive README for python sdk
Covers installation, quick start, async usage, authentication with
FastAPI example, error handling, teams, self-hosted config, full API
reference, and development/testing instructions.
2026-03-25 08:10:59 +01:00
Ejiro Asiuwhu
625e3c06b6 test: add end-to-end integration test suite for live Stack Auth validation
Covers user CRUD lifecycle, team management, sessions, API key management,
authenticate_request with real request shapes, and async variants.
Requires STACK_E2E=1 env var and running Stack Auth instance.
2026-03-25 08:10:51 +01:00
Ejiro Asiuwhu
b7e21a02e8 fix: replace broad exception catches with specific types and add debug logging
- _decode_jwt_payload catches only ValueError and json.JSONDecodeError
- Token refresh catches httpx.HTTPError, ValueError, KeyError with debug log
- authenticate_request catches jwt.PyJWTError, httpx.HTTPError, ValueError, KeyError
- Added docstring to _token_store_registry explaining purpose and test reset
- Updated test mocks to use jwt.PyJWTError instead of bare Exception
2026-03-25 08:08:12 +01:00
Ejiro Asiuwhu
7ec2e27a8e fix: add debug logging to authenticate_request exception handlers
- Add logger = logging.getLogger("stack_auth") at module level
- Emit logger.debug("authenticate_request failed", exc_info=True) in both sync and async handlers
- Add tests verifying debug log emission on verification failure
2026-03-25 08:02:10 +01:00
Ejiro Asiuwhu
bfff27d4e7 fix: make RequestLike runtime_checkable and remove type: ignore from resolve_token_store
- Add @runtime_checkable decorator to RequestLike Protocol
- Replace bare fallback with isinstance(init, RequestLike) check in resolve_token_store
- Raise TypeError for invalid token store initializer types
- Add tests for isinstance checks and invalid input handling
2026-03-25 08:00:59 +01:00
Ejiro Asiuwhu
6c0c58d2eb feat: add get_partial_user method to StackServerApp and AsyncStackServerApp
- Decode JWT access token claims locally without network request
- Return TokenPartialUser with user ID, display name, email, and flags
- Return None when no token store or no access token available
- Support token_store override parameter for per-call flexibility
- Method is synchronous on both classes since it performs no I/O
2026-03-25 07:53:35 +01:00
Ejiro Asiuwhu
80f2c32f2e test: add failing tests for get_partial_user method
- Test get_partial_user returns TokenPartialUser from valid JWT
- Test returns None when no token store, explicit None, or empty store
- Test token_store override parameter
- Test async variant works identically (sync method, no I/O)
2026-03-25 07:52:42 +01:00
Ejiro Asiuwhu
09595481dd fix: add publishable_client_key parameter and fix token store defaults
- Add publishable_client_key param to BaseAPIClient, StackServerApp, AsyncStackServerApp
- Include x-stack-publishable-client-key header when key is provided
- Change ExplicitTokenStore defaults from empty string to None
- Remove empty string fallback in resolve_token_store dict branch
2026-03-25 07:51:38 +01:00
Ejiro Asiuwhu
6138ff0997 test: add failing tests for publishable_client_key and token store defaults
- Test publishable_client_key header included/omitted in _build_headers
- Test ExplicitTokenStore defaults to None without arguments
- Test resolve_token_store dict branch defaults missing keys to None
- Test StackServerApp/AsyncStackServerApp accept publishable_client_key
2026-03-25 07:50:42 +01:00
Ejiro Asiuwhu
0f39d103ae fix: make metadata fields nullable on user and team models
The Stack Auth API returns null for client_metadata, client_read_only_metadata,
and server_metadata when not set, but the models expected dict. Changed to
dict | None to handle null responses gracefully.
2026-03-25 05:28:42 +01:00
Ejiro Asiuwhu
1343d3ab2f test: add integration tests for payment, email, and data vault methods
- Add sync+async tests for list_products, get_item, grant_product, cancel_subscription
- Add tests for ServerItem.increase_quantity, decrease_quantity, try_decrease_quantity
- Add sync+async tests for send_email (html, text, multi-recipient) and get_email_delivery_stats
- Add sync+async tests for DataVaultStore get, set, delete, list_keys with NotFoundError handling
- Coverage grows from 299 to 337 tests, all passing
2026-03-25 02:26:45 +01:00
Ejiro Asiuwhu
c4ee7daa45 docs: add module docstring with quick-start example and complete PyPI metadata
- Replace one-line module docstring with comprehensive quick-start guide
  covering sync, async, and error handling usage patterns
- Add Framework :: Pydantic :: 2 and Topic classifiers to pyproject.toml
- Add keywords for PyPI discoverability
- Add project.urls section with Homepage, Repository, Documentation, Bug Tracker
2026-03-25 02:05:44 +01:00
Ejiro Asiuwhu
f3780f2c60 docs: add comprehensive Google-style docstrings to all public SDK methods
- Add Args, Returns, and Raises sections to all public methods on
  StackServerApp and AsyncStackServerApp (~46 methods each)
- Expand class-level docstrings with full constructor and usage examples
- Document return-None-on-404 behavior, ValueError raises, and error types
2026-03-25 02:04:58 +01:00
Ejiro Asiuwhu
23c9f850b9 feat: add get_data_vault_store to facade classes and exports
- StackServerApp.get_data_vault_store returns sync DataVaultStore
- AsyncStackServerApp.get_data_vault_store returns AsyncDataVaultStore
- Export DataVaultStore and AsyncDataVaultStore from package root
2026-03-25 01:52:46 +01:00
Ejiro Asiuwhu
299718c9db feat: add data vault store with key-value operations
- DataVaultStore class with sync get/set/delete/list_keys methods
- AsyncDataVaultStore class with async variants
- Export both from models package
2026-03-25 01:51:59 +01:00
Ejiro Asiuwhu
c5ab83984f feat: add payment methods and email sending to StackServerApp
- Add _resolve_customer_path helper for polymorphic customer identification
- Add list_products, get_item, grant_product, cancel_subscription to both sync and async facades
- Add send_email and get_email_delivery_stats to both sync and async facades
- Export ServerItem, AsyncServerItem, EmailDeliveryInfo from package root
2026-03-25 01:49:12 +01:00
Ejiro Asiuwhu
0e6afdc9a7 feat: add ServerItem, AsyncServerItem, and EmailDeliveryInfo models
- ServerItem wraps Item with increase/decrease/tryDecrease quantity methods
- AsyncServerItem provides async counterpart for quantity operations
- EmailDeliveryInfo model for delivery statistics (delivered/bounced/complained/total)
- Export new models from models package
2026-03-25 01:47:35 +01:00
Ejiro Asiuwhu
e8ce41bfbb fix: update team member profile test to include required user_id field
The user_id field was added to TeamMemberProfile during team management
implementation but the pre-existing model test was not updated.
2026-03-25 01:39:56 +01:00
Ejiro Asiuwhu
eae4842ab1 feat: add oauth provider methods and connected accounts to both facades
- create_oauth_provider links OAuth provider to a user
- list_oauth_providers and get_oauth_provider for provider lookup
- list_connected_accounts as alias for list_oauth_providers
- Sync and async versions on both facade classes with tests
2026-03-25 01:37:25 +01:00
Ejiro Asiuwhu
7e41fc8f95 feat: add api key management methods to both facades
- User API key create/list/revoke on StackServerApp and AsyncStackServerApp
- Team API key create/list/revoke on both facades
- check_api_key validates any key and returns user_id/team_id
- Tests for all sync and async API key methods
2026-03-25 01:35:44 +01:00
Ejiro Asiuwhu
adeb156d1a feat: add contact channel verification methods to StackServerApp
- list_contact_channels, create_contact_channel for user channels
- send_verification_code with optional callback URL
- verify_contact_channel to validate codes
- Both sync and async facades
2026-03-25 01:31:32 +01:00
Ejiro Asiuwhu
c2f3a3a03a test: add failing tests for contact channel verification methods
- Tests for list, create, send verification, verify on sync facade
- Tests for all contact channel methods on async facade
- Covers optional fields and callback URL parameter
2026-03-25 01:30:07 +01:00
Ejiro Asiuwhu
e26f3a228e feat: add permission management methods to StackServerApp
- grant_permission, revoke_permission for team and project scope
- list_permissions with optional direct filter
- has_permission boolean check, get_permission single lookup
- Both sync and async facades
2026-03-25 01:29:12 +01:00
Ejiro Asiuwhu
6b012c02d7 test: add failing tests for permission management methods
- Tests for grant, revoke, list, has, get permission on sync facade
- Tests for all permission methods on async facade
- Covers team-scoped and project-level permissions
2026-03-25 01:28:28 +01:00
Ejiro Asiuwhu
cd64ecb703 feat: add team membership, invitations, and member profiles
- Add add_team_member and remove_team_member via /team-memberships
- Add send_team_invitation, list_team_invitations, revoke_team_invitation
- Add list_team_member_profiles and get_team_member_profile
- Add user_id field to TeamMemberProfile for profile filtering
- Both sync and async facades have identical method surfaces
2026-03-25 01:24:57 +01:00
Ejiro Asiuwhu
c48b9ca7fc test: add failing tests for team membership, invitations, and profiles
- Tests for add_team_member, remove_team_member
- Tests for send/list/revoke team invitations
- Tests for list_team_member_profiles and get_team_member_profile
- Async equivalents for all membership/invitation/profile operations
2026-03-25 01:24:01 +01:00
Ejiro Asiuwhu
3bf752f120 feat: add team CRUD and API key lookup to StackServerApp
- Add get_team, list_teams, create_team, update_team, delete_team
- Add get_team_by_api_key with two-step lookup via /api-keys/check
- Import ServerTeam, TeamInvitation, TeamMemberProfile models
- Both sync and async facades have identical method surfaces
- Uses _UNSET sentinel pattern for update_team (same as update_user)
2026-03-25 01:22:59 +01:00
Ejiro Asiuwhu
3094338b42 test: add failing tests for team CRUD methods
- Tests for get_team, list_teams, create_team, update_team, delete_team
- Tests for get_team_by_api_key two-step lookup
- Async equivalents for all team CRUD operations
- Uses respx mocking pattern consistent with existing user tests
2026-03-25 01:22:09 +01:00
Ejiro Asiuwhu
7f4e3eb428 feat: add session management methods to StackServerApp and AsyncStackServerApp
- list_sessions(user_id) returns list of ActiveSession for a user
- get_session(session_id, user_id) filters from list_sessions, returns ActiveSession or None
- revoke_session(session_id, user_id) deletes session via DELETE endpoint
- Both sync and async facades implement all three methods
2026-03-25 01:11:22 +01:00
Ejiro Asiuwhu
e1ccb718f4 test: add failing tests for session management methods
- list_sessions, get_session, revoke_session tests for sync and async
- Tests verify user_id query param, empty results, session filtering
2026-03-25 01:10:41 +01:00
Ejiro Asiuwhu
7eb0c25dc6 feat: export StackServerApp and AsyncStackServerApp from package root
- Add top-level imports so users can write: from stack_auth import StackServerApp
- Remove outdated comment about Phase 3 adding facades
2026-03-25 01:07:48 +01:00
Ejiro Asiuwhu
fd3a816700 feat: add StackServerApp and AsyncStackServerApp with user CRUD
- StackServerApp (sync) and AsyncStackServerApp (async) facade classes
- get_user returns ServerUser or None on 404
- list_users with cursor/limit/order_by/desc/query/include_restricted/include_anonymous
- create_user sends only non-None fields
- update_user uses _UNSET sentinel to distinguish not-provided from explicit None
- delete_user returns None
- get_user_by_api_key performs two-step lookup via /api-keys/check then /users/{id}
- Both classes support context managers and compose API client + optional token store
2026-03-25 01:07:02 +01:00
Ejiro Asiuwhu
13f88755e3 test: add failing tests for StackServerApp and AsyncStackServerApp
- Tests for get_user, list_users, create_user, update_user, delete_user
- Tests for get_user_by_api_key two-step lookup
- Tests for sentinel pattern in update_user (None vs not-provided)
- Both sync and async facades covered with respx mocks
2026-03-25 01:05:49 +01:00
Ejiro Asiuwhu
6b1138981d feat: add token store with CAS-based refresh and dual locks
- TokenStore ABC with get_stored_access_token, get_stored_refresh_token, compare_and_set
- MemoryTokenStore, ExplicitTokenStore, RequestTokenStore implementations
- Module-level registry shares MemoryTokenStore instances per project_id
- resolve_token_store handles memory/dict/request/None init variants
- CAS refresh algorithm (sync + async) with 20s expiry buffer and 75s iat threshold
- Token refresh via form-encoded POST to /api/v1/auth/oauth/token
- Per-instance threading.Lock and asyncio.Lock for concurrency safety
- 34 passing tests covering all store types, registry, helpers, and CAS branches
2026-03-25 00:51:40 +01:00
Ejiro Asiuwhu
8060bc67ff feat: export auth types and functions from stack_auth package
- Add AuthState, TokenPartialUser, decode_access_token_claims to public API
- Add sync_authenticate_request and async_authenticate_request exports
- Existing exports unchanged
2026-03-25 00:51:27 +01:00
Ejiro Asiuwhu
3ca550191b feat: add authenticate_request with jwt verification and partial decode
- TokenPartialUser frozen dataclass for unverified JWT payload extraction
- AuthState frozen dataclass for authentication results
- decode_access_token_claims extracts user info without signature verification
- _extract_token_from_headers handles Authorization and x-stack-auth headers
- sync_authenticate_request and async_authenticate_request compose with JWKS verifier
2026-03-25 00:50:59 +01:00