Repository navigation
feat(auth): add Python authentication facade #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 6 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
1b48e8d
feat(auth): add Python authentication state
swkeever 8c706f5
feat(auth): expand Python authentication transport
swkeever 36f924e
feat(auth): implement Python session lifecycle
swkeever b0fb9d6
feat(auth): complete Python authentication facade
swkeever 8961e07
test(auth): bind Python authentication contract
swkeever 5076d1d
chore: enforce strict quality gates
swkeever a8968ec
fix: address authentication review findings
swkeever 2e0416e
fix: synchronize authentication state
swkeever b1b64d8
fix: make authentication state atomic
swkeever 8ba1ceb
fix: serialize authentication transitions
swkeever d8683b6
fix: distinguish provider authorization failures
swkeever ee8c528
fix: preserve authentication invariants
swkeever 7cdeda3
fix: serialize authentication state transitions
swkeever c1c0aee
fix: enforce authentication ownership boundaries
swkeever cdbfac3
fix: preserve authenticated operation guarantees
swkeever 3af1245
fix: serialize auth event delivery
swkeever c8d8f9c
fix: retry invalidated realtime subscriptions
swkeever 175a4b1
fix: identify restored device sessions
swkeever 3ad6a97
chore: require compatible Ruff version
swkeever ae086d6
fix: use structured provider errors
swkeever b8e15d7
feat: expose session cursor pagination
swkeever 2db7f0e
fix: harden authentication concurrency
swkeever 5dd73de
fix: normalize authentication failures
swkeever c926391
fix: harden realtime auth transitions
swkeever 933d2a8
fix(auth): close facade review gaps
swkeever 8cbe66f
fix(auth): refresh user after method promotion
swkeever 2ce360e
test(realtime): await scheduled auth cleanup
swkeever 7a1847b
feat(auth): complete stable authentication surface
swkeever 844d19e
fix(realtime): fence publishes across auth changes
swkeever cb49343
fix(auth): align device verification contract
swkeever 118bb00
docs(auth): clarify platform exchange eligibility
swkeever c92c2e6
fix(auth): close concurrency review gaps
swkeever f501c6d
fix(auth): fence stale asynchronous state
swkeever dfaa12a
fix(auth): complete realtime cleanup barriers
swkeever 9c743df
fix(realtime): drain completed cleanup snapshots
swkeever dfb2a2b
fix(auth): reconcile validated response state
swkeever 3a9cbc0
fix(auth): preserve listener semantics
swkeever File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| # Authentication | ||
|
|
||
| Create a client with your project URL and anonymous key, then use `client.auth` | ||
| for account and session operations. | ||
|
|
||
| ```python | ||
| from volcano_sdk import VolcanoClient | ||
|
|
||
| client = VolcanoClient( | ||
| api_url="https://api.volcano.dev", | ||
| anon_key="your-anon-key", | ||
| ) | ||
|
|
||
| session = client.auth.sign_in( | ||
| email="user@example.com", | ||
| password="your-password", | ||
| ) | ||
|
|
||
| print(client.current_user.id) | ||
| print(session.expires_at) | ||
| ``` | ||
|
|
||
| The client keeps the active `current_user` and `current_session` in memory. | ||
| Pass both tokens when restoring an existing session in a new client. An access | ||
| token without a refresh token is valid, but a refresh token without an access | ||
| token is rejected. | ||
|
|
||
| ```python | ||
| restored = VolcanoClient( | ||
| api_url="https://api.volcano.dev", | ||
| anon_key="your-anon-key", | ||
| access_token=session.access_token, | ||
| refresh_token=session.refresh_token, | ||
| ) | ||
|
|
||
| restored.auth.refresh_session() | ||
| ``` | ||
|
|
||
| Store tokens in the secure storage provided by your runtime. Do not log them or | ||
| place them in source control. | ||
|
|
||
| ## Manage the session | ||
|
|
||
| Subscribe to auth-state changes when application state must follow the client. | ||
| The listener runs immediately with the current user and after committed auth | ||
| changes. Call the returned function to unsubscribe. | ||
|
|
||
| ```python | ||
| unsubscribe = client.auth.on_auth_state_change( | ||
| lambda user: print("signed in" if user else "signed out") | ||
| ) | ||
|
|
||
| client.auth.refresh_session() | ||
| client.auth.sign_out() | ||
| unsubscribe() | ||
| ``` | ||
|
|
||
| A failed refresh clears local authentication so stale credentials are not | ||
| reused. `sign_out()` also clears local state if the remote revoke fails. | ||
|
|
||
| List and revoke device sessions through the same facade: | ||
|
|
||
| ```python | ||
| page = client.auth.get_sessions(page=1, limit=20) | ||
|
|
||
| for device_session in page.sessions: | ||
| print(device_session.id, device_session.last_seen_at) | ||
|
swkeever marked this conversation as resolved.
Resolved
|
||
|
|
||
| client.auth.delete_session(session_id="session-id") | ||
| client.auth.delete_all_other_sessions() | ||
| ``` | ||
|
|
||
| ## Create and update accounts | ||
|
|
||
| Sign-up can return without a session when email confirmation is required. | ||
|
|
||
| ```python | ||
| result = client.auth.sign_up( | ||
| email="new-user@example.com", | ||
| password="your-password", | ||
| user_metadata={"plan": "starter"}, | ||
| ) | ||
|
|
||
| if result.confirmation_required: | ||
| print("Check your email") | ||
| ``` | ||
|
|
||
| Update the current user, or start with an anonymous account and preserve its | ||
| identity when converting it: | ||
|
|
||
| ```python | ||
| client.auth.update_user(user_metadata={"plan": "pro"}) | ||
|
|
||
| client.auth.sign_out() | ||
| client.auth.sign_up_anonymous(user_metadata={"source": "demo"}) | ||
| anonymous_id = client.current_user.id | ||
|
|
||
| converted = client.auth.convert_anonymous( | ||
| email="converted@example.com", | ||
| password="your-password", | ||
| ) | ||
| assert converted.id == anonymous_id | ||
| ``` | ||
|
|
||
| Email workflows are available as explicit operations: | ||
|
|
||
| ```python | ||
| client.auth.resend_confirmation(email="new-user@example.com") | ||
| client.auth.confirm_email(token="confirmation-token") | ||
| client.auth.forgot_password(email="user@example.com") | ||
| client.auth.reset_password( | ||
| token="recovery-token", | ||
| new_password="your-new-password", | ||
| ) | ||
|
|
||
| change = client.auth.request_email_change(new_email="next@example.com") | ||
| client.auth.confirm_email_change(token="email-change-token") | ||
| # Or cancel a pending request: | ||
| client.auth.cancel_email_change() | ||
| ``` | ||
|
|
||
| ## Open hosted auth and OAuth | ||
|
|
||
| Hosted auth returns a URL and generated state value for your application to | ||
| retain before navigation: | ||
|
|
||
| ```python | ||
| request = client.auth.get_hosted_auth_url( | ||
| project_id="project-id", | ||
| action="login", | ||
| ) | ||
| print(request.authorization_url) | ||
| ``` | ||
|
|
||
| OAuth authorization follows the same pattern. Preserve `request.state` and | ||
| pass it as `expected_state` during exchange; the SDK rejects a mismatch before | ||
| calling the API. | ||
|
|
||
| ```python | ||
| request = client.auth.get_oauth_authorization_url( | ||
| provider="github", | ||
| redirect_url="https://app.example.com/auth/callback", | ||
| ) | ||
|
|
||
| # After the provider redirects to your application: | ||
| client.auth.exchange_oauth_code( | ||
| code="authorization-code", | ||
| redirect_url="https://app.example.com/auth/callback", | ||
| state="state-from-callback", | ||
| expected_state=request.state, | ||
| ) | ||
| ``` | ||
|
|
||
| Signed-in users can link providers, inspect them, refresh provider tokens, and | ||
| call provider APIs through Volcano: | ||
|
|
||
| ```python | ||
| link = client.auth.link_oauth_provider( | ||
| provider="github", | ||
| redirect_url="https://app.example.com/auth/link/callback", | ||
| ) | ||
|
|
||
| providers = client.auth.get_linked_oauth_providers() | ||
| token = client.auth.get_oauth_provider_token(provider="github") | ||
| client.auth.refresh_oauth_token(provider="github") | ||
| profile = client.auth.call_oauth_api( | ||
| provider="github", | ||
| endpoint="/user", | ||
| ) | ||
| client.auth.unlink_oauth_provider(provider="github") | ||
| ``` | ||
|
|
||
| Keep generated state values and provider tokens secret. Navigate to the returned | ||
| authorization URL only after storing its matching state value. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.