go-authgate 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- go_authgate-0.1.0/.github/workflows/release.yml +33 -0
- go_authgate-0.1.0/.github/workflows/testing.yml +36 -0
- go_authgate-0.1.0/.github/workflows/trivy.yml +35 -0
- go_authgate-0.1.0/.gitignore +15 -0
- go_authgate-0.1.0/CLAUDE.md +44 -0
- go_authgate-0.1.0/LICENSE +21 -0
- go_authgate-0.1.0/Makefile +24 -0
- go_authgate-0.1.0/PKG-INFO +142 -0
- go_authgate-0.1.0/README.md +108 -0
- go_authgate-0.1.0/pyproject.toml +90 -0
- go_authgate-0.1.0/src/authgate/__init__.py +137 -0
- go_authgate-0.1.0/src/authgate/_version.py +3 -0
- go_authgate-0.1.0/src/authgate/authflow/__init__.py +18 -0
- go_authgate-0.1.0/src/authgate/authflow/authcode.py +138 -0
- go_authgate-0.1.0/src/authgate/authflow/browser.py +43 -0
- go_authgate-0.1.0/src/authgate/authflow/device.py +120 -0
- go_authgate-0.1.0/src/authgate/authflow/pkce.py +28 -0
- go_authgate-0.1.0/src/authgate/authflow/token_source.py +126 -0
- go_authgate-0.1.0/src/authgate/clientcreds/__init__.py +11 -0
- go_authgate-0.1.0/src/authgate/clientcreds/token_source.py +121 -0
- go_authgate-0.1.0/src/authgate/clientcreds/transport.py +37 -0
- go_authgate-0.1.0/src/authgate/credstore/__init__.py +54 -0
- go_authgate-0.1.0/src/authgate/credstore/codecs.py +45 -0
- go_authgate-0.1.0/src/authgate/credstore/file_store.py +154 -0
- go_authgate-0.1.0/src/authgate/credstore/keyring_store.py +68 -0
- go_authgate-0.1.0/src/authgate/credstore/models.py +28 -0
- go_authgate-0.1.0/src/authgate/credstore/protocols.py +43 -0
- go_authgate-0.1.0/src/authgate/credstore/secure_store.py +100 -0
- go_authgate-0.1.0/src/authgate/discovery/__init__.py +6 -0
- go_authgate-0.1.0/src/authgate/discovery/async_client.py +72 -0
- go_authgate-0.1.0/src/authgate/discovery/client.py +109 -0
- go_authgate-0.1.0/src/authgate/discovery/models.py +43 -0
- go_authgate-0.1.0/src/authgate/exceptions.py +45 -0
- go_authgate-0.1.0/src/authgate/middleware/__init__.py +11 -0
- go_authgate-0.1.0/src/authgate/middleware/core.py +67 -0
- go_authgate-0.1.0/src/authgate/middleware/django.py +105 -0
- go_authgate-0.1.0/src/authgate/middleware/fastapi.py +95 -0
- go_authgate-0.1.0/src/authgate/middleware/flask.py +103 -0
- go_authgate-0.1.0/src/authgate/middleware/models.py +20 -0
- go_authgate-0.1.0/src/authgate/oauth/__init__.py +21 -0
- go_authgate-0.1.0/src/authgate/oauth/async_client.py +237 -0
- go_authgate-0.1.0/src/authgate/oauth/client.py +237 -0
- go_authgate-0.1.0/src/authgate/oauth/models.py +96 -0
- go_authgate-0.1.0/src/authgate/py.typed +0 -0
- go_authgate-0.1.0/tests/__init__.py +0 -0
- go_authgate-0.1.0/tests/conftest.py +21 -0
- go_authgate-0.1.0/tests/test_authflow.py +209 -0
- go_authgate-0.1.0/tests/test_clientcreds.py +146 -0
- go_authgate-0.1.0/tests/test_credstore.py +184 -0
- go_authgate-0.1.0/tests/test_discovery.py +139 -0
- go_authgate-0.1.0/tests/test_middleware.py +149 -0
- go_authgate-0.1.0/tests/test_oauth.py +301 -0
- go_authgate-0.1.0/uv.lock +1228 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
uses: ./.github/workflows/testing.yml
|
|
10
|
+
|
|
11
|
+
trivy:
|
|
12
|
+
uses: ./.github/workflows/trivy.yml
|
|
13
|
+
|
|
14
|
+
publish:
|
|
15
|
+
needs: [test, trivy]
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
id-token: write
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v6
|
|
25
|
+
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
run: uv python install 3.12
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: uv build
|
|
31
|
+
|
|
32
|
+
- name: Publish to PyPI
|
|
33
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Testing
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_call:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v6
|
|
22
|
+
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
run: uv python install ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: make install
|
|
28
|
+
|
|
29
|
+
- name: Lint
|
|
30
|
+
run: make lint
|
|
31
|
+
|
|
32
|
+
- name: Type check
|
|
33
|
+
run: make typecheck
|
|
34
|
+
|
|
35
|
+
- name: Test
|
|
36
|
+
run: make test
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Trivy Security Scan
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: "0 15 * * *" # 23:00 UTC+8
|
|
10
|
+
workflow_call:
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
trivy:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
security-events: write
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Trivy vulnerability scan
|
|
22
|
+
uses: aquasecurity/trivy-action@0.35.0
|
|
23
|
+
with:
|
|
24
|
+
scan-type: fs
|
|
25
|
+
scan-ref: .
|
|
26
|
+
format: sarif
|
|
27
|
+
output: trivy-results.sarif
|
|
28
|
+
exit-code: 1
|
|
29
|
+
severity: CRITICAL,HIGH
|
|
30
|
+
|
|
31
|
+
- name: Upload results to GitHub Security tab
|
|
32
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
33
|
+
if: always()
|
|
34
|
+
with:
|
|
35
|
+
sarif_file: trivy-results.sarif
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code when working with the Python SDK.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Python SDK for AuthGate — mirrors the Go SDK's architecture using idiomatic Python patterns.
|
|
8
|
+
|
|
9
|
+
Package: `authgate` (Python 3.10+, src layout with hatchling build)
|
|
10
|
+
|
|
11
|
+
## Common Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
make install # uv sync --all-extras (install all deps)
|
|
15
|
+
make test # Run all tests with pytest
|
|
16
|
+
make lint # Run ruff linter
|
|
17
|
+
make fmt # Format code with ruff
|
|
18
|
+
make typecheck # Run mypy strict
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Project is managed with [uv](https://docs.astral.sh/uv/). All `make` targets use `uv run` so no manual venv activation is needed.
|
|
22
|
+
|
|
23
|
+
## Code Style
|
|
24
|
+
|
|
25
|
+
- ruff for linting and formatting (line length 100)
|
|
26
|
+
- mypy strict mode
|
|
27
|
+
- Dataclasses over Pydantic (zero extra deps)
|
|
28
|
+
- Sync + Async dual API in separate client classes
|
|
29
|
+
- Framework-specific middleware: users only import what they need
|
|
30
|
+
- `from __future__ import annotations` in all modules
|
|
31
|
+
|
|
32
|
+
## Architecture
|
|
33
|
+
|
|
34
|
+
- `oauth/` — Pure HTTP client layer (sync: `OAuthClient`, async: `AsyncOAuthClient`)
|
|
35
|
+
- `discovery/` — OIDC auto-discovery with caching
|
|
36
|
+
- `credstore/` — Generic credential storage (file, keyring, composite secure store)
|
|
37
|
+
- `authflow/` — Device Code flow, Auth Code + PKCE, auto-refresh TokenSource
|
|
38
|
+
- `middleware/` — Framework adapters (FastAPI, Flask, Django)
|
|
39
|
+
- `clientcreds/` — M2M Client Credentials with auto-caching
|
|
40
|
+
- `__init__.py` — `authenticate()` / `async_authenticate()` entry points
|
|
41
|
+
|
|
42
|
+
## Before Committing
|
|
43
|
+
|
|
44
|
+
All code must pass `make lint`, `make fmt`, and `make typecheck` before committing.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 AuthGate Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.PHONY: test lint fmt typecheck install clean
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
uv sync --all-extras
|
|
5
|
+
|
|
6
|
+
test:
|
|
7
|
+
uv run pytest tests/ -v --tb=short
|
|
8
|
+
|
|
9
|
+
coverage:
|
|
10
|
+
uv run coverage run -m pytest tests/ -v --tb=short
|
|
11
|
+
uv run coverage report -m
|
|
12
|
+
|
|
13
|
+
lint:
|
|
14
|
+
uv run ruff check src/ tests/
|
|
15
|
+
|
|
16
|
+
fmt:
|
|
17
|
+
uv run ruff format src/ tests/
|
|
18
|
+
uv run ruff check --fix src/ tests/
|
|
19
|
+
|
|
20
|
+
typecheck:
|
|
21
|
+
uv run mypy src/authgate/
|
|
22
|
+
|
|
23
|
+
clean:
|
|
24
|
+
rm -rf build/ dist/ *.egg-info src/*.egg-info .mypy_cache .pytest_cache .ruff_cache .coverage htmlcov/
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: go-authgate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for AuthGate — OAuth 2.0 authentication and token management
|
|
5
|
+
Author: AuthGate Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: httpx<1,>=0.27
|
|
19
|
+
Requires-Dist: keyring<27,>=25
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: coverage>=7; extra == 'dev'
|
|
22
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
27
|
+
Provides-Extra: django
|
|
28
|
+
Requires-Dist: django>=4.2; extra == 'django'
|
|
29
|
+
Provides-Extra: fastapi
|
|
30
|
+
Requires-Dist: fastapi>=0.100; extra == 'fastapi'
|
|
31
|
+
Provides-Extra: flask
|
|
32
|
+
Requires-Dist: flask>=2.3; extra == 'flask'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# AuthGate Python SDK
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/go-authgate/)
|
|
38
|
+
[](https://pypi.org/project/go-authgate/)
|
|
39
|
+
[](https://github.com/go-authgate/sdk-python/actions/workflows/testing.yml)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
|
|
42
|
+
Python SDK for [AuthGate](https://github.com/go-authgate) — OAuth 2.0 authentication and token management.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install go-authgate
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
With framework support:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install go-authgate[fastapi]
|
|
54
|
+
pip install go-authgate[flask]
|
|
55
|
+
pip install go-authgate[django]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from authgate import authenticate
|
|
62
|
+
|
|
63
|
+
client, token = authenticate(
|
|
64
|
+
"https://auth.example.com",
|
|
65
|
+
"my-client-id",
|
|
66
|
+
scopes=["profile", "email"],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
print(f"Access token: {token.access_token}")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Async Usage
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from authgate import async_authenticate
|
|
76
|
+
|
|
77
|
+
client, token = await async_authenticate(
|
|
78
|
+
"https://auth.example.com",
|
|
79
|
+
"my-client-id",
|
|
80
|
+
scopes=["profile", "email"],
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Client Credentials (M2M)
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from authgate.oauth import OAuthClient, Endpoints
|
|
88
|
+
from authgate.clientcreds import TokenSource, BearerAuth
|
|
89
|
+
import httpx
|
|
90
|
+
|
|
91
|
+
client = OAuthClient("my-service", endpoints, client_secret="secret")
|
|
92
|
+
ts = TokenSource(client, scopes=["api"])
|
|
93
|
+
|
|
94
|
+
# Auto-attaches Bearer token to every request
|
|
95
|
+
with httpx.Client(auth=BearerAuth(ts)) as http:
|
|
96
|
+
resp = http.get("https://api.example.com/data")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Middleware
|
|
100
|
+
|
|
101
|
+
### FastAPI
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from fastapi import FastAPI, Depends
|
|
105
|
+
from authgate.middleware.fastapi import BearerAuth
|
|
106
|
+
from authgate.middleware.models import TokenInfo
|
|
107
|
+
|
|
108
|
+
app = FastAPI()
|
|
109
|
+
auth = BearerAuth(oauth_client)
|
|
110
|
+
|
|
111
|
+
@app.get("/protected")
|
|
112
|
+
async def protected(info: TokenInfo = Depends(auth)):
|
|
113
|
+
return {"user": info.user_id}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Flask
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from flask import Flask
|
|
120
|
+
from authgate.middleware.flask import bearer_auth, get_token_info
|
|
121
|
+
|
|
122
|
+
app = Flask(__name__)
|
|
123
|
+
|
|
124
|
+
@app.route("/protected")
|
|
125
|
+
@bearer_auth(oauth_client)
|
|
126
|
+
def protected():
|
|
127
|
+
info = get_token_info()
|
|
128
|
+
return {"user": info.user_id}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
make install # uv sync --all-extras
|
|
135
|
+
make test
|
|
136
|
+
make lint
|
|
137
|
+
make typecheck
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# AuthGate Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/go-authgate/)
|
|
4
|
+
[](https://pypi.org/project/go-authgate/)
|
|
5
|
+
[](https://github.com/go-authgate/sdk-python/actions/workflows/testing.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Python SDK for [AuthGate](https://github.com/go-authgate) — OAuth 2.0 authentication and token management.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install go-authgate
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
With framework support:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install go-authgate[fastapi]
|
|
20
|
+
pip install go-authgate[flask]
|
|
21
|
+
pip install go-authgate[django]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from authgate import authenticate
|
|
28
|
+
|
|
29
|
+
client, token = authenticate(
|
|
30
|
+
"https://auth.example.com",
|
|
31
|
+
"my-client-id",
|
|
32
|
+
scopes=["profile", "email"],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
print(f"Access token: {token.access_token}")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Async Usage
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from authgate import async_authenticate
|
|
42
|
+
|
|
43
|
+
client, token = await async_authenticate(
|
|
44
|
+
"https://auth.example.com",
|
|
45
|
+
"my-client-id",
|
|
46
|
+
scopes=["profile", "email"],
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Client Credentials (M2M)
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from authgate.oauth import OAuthClient, Endpoints
|
|
54
|
+
from authgate.clientcreds import TokenSource, BearerAuth
|
|
55
|
+
import httpx
|
|
56
|
+
|
|
57
|
+
client = OAuthClient("my-service", endpoints, client_secret="secret")
|
|
58
|
+
ts = TokenSource(client, scopes=["api"])
|
|
59
|
+
|
|
60
|
+
# Auto-attaches Bearer token to every request
|
|
61
|
+
with httpx.Client(auth=BearerAuth(ts)) as http:
|
|
62
|
+
resp = http.get("https://api.example.com/data")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Middleware
|
|
66
|
+
|
|
67
|
+
### FastAPI
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from fastapi import FastAPI, Depends
|
|
71
|
+
from authgate.middleware.fastapi import BearerAuth
|
|
72
|
+
from authgate.middleware.models import TokenInfo
|
|
73
|
+
|
|
74
|
+
app = FastAPI()
|
|
75
|
+
auth = BearerAuth(oauth_client)
|
|
76
|
+
|
|
77
|
+
@app.get("/protected")
|
|
78
|
+
async def protected(info: TokenInfo = Depends(auth)):
|
|
79
|
+
return {"user": info.user_id}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Flask
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from flask import Flask
|
|
86
|
+
from authgate.middleware.flask import bearer_auth, get_token_info
|
|
87
|
+
|
|
88
|
+
app = Flask(__name__)
|
|
89
|
+
|
|
90
|
+
@app.route("/protected")
|
|
91
|
+
@bearer_auth(oauth_client)
|
|
92
|
+
def protected():
|
|
93
|
+
info = get_token_info()
|
|
94
|
+
return {"user": info.user_id}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Development
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
make install # uv sync --all-extras
|
|
101
|
+
make test
|
|
102
|
+
make lint
|
|
103
|
+
make typecheck
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "go-authgate"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Python SDK for AuthGate — OAuth 2.0 authentication and token management"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "AuthGate Contributors" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
dependencies = ["httpx>=0.27,<1", "keyring>=25,<27"]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
fastapi = ["fastapi>=0.100"]
|
|
28
|
+
flask = ["flask>=2.3"]
|
|
29
|
+
django = ["django>=4.2"]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest>=8",
|
|
32
|
+
"pytest-asyncio>=0.23",
|
|
33
|
+
"pytest-httpx>=0.30",
|
|
34
|
+
"coverage>=7",
|
|
35
|
+
"mypy>=1.10",
|
|
36
|
+
"ruff>=0.5",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[tool.hatch.version]
|
|
40
|
+
path = "src/authgate/_version.py"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/authgate"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
target-version = "py310"
|
|
47
|
+
line-length = 100
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = [
|
|
51
|
+
"E",
|
|
52
|
+
"F",
|
|
53
|
+
"W",
|
|
54
|
+
"I",
|
|
55
|
+
"N",
|
|
56
|
+
"UP",
|
|
57
|
+
"B",
|
|
58
|
+
"A",
|
|
59
|
+
"SIM",
|
|
60
|
+
"RUF",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.isort]
|
|
64
|
+
known-first-party = ["authgate"]
|
|
65
|
+
|
|
66
|
+
[tool.mypy]
|
|
67
|
+
python_version = "3.10"
|
|
68
|
+
strict = true
|
|
69
|
+
warn_return_any = true
|
|
70
|
+
warn_unused_configs = true
|
|
71
|
+
|
|
72
|
+
[[tool.mypy.overrides]]
|
|
73
|
+
module = ["keyring", "keyring.errors"]
|
|
74
|
+
ignore_missing_imports = true
|
|
75
|
+
|
|
76
|
+
[[tool.mypy.overrides]]
|
|
77
|
+
module = ["fastapi", "fastapi.*"]
|
|
78
|
+
ignore_missing_imports = true
|
|
79
|
+
|
|
80
|
+
[[tool.mypy.overrides]]
|
|
81
|
+
module = ["flask", "flask.*"]
|
|
82
|
+
ignore_missing_imports = true
|
|
83
|
+
|
|
84
|
+
[[tool.mypy.overrides]]
|
|
85
|
+
module = ["django", "django.*"]
|
|
86
|
+
ignore_missing_imports = true
|
|
87
|
+
|
|
88
|
+
[tool.pytest.ini_options]
|
|
89
|
+
testpaths = ["tests"]
|
|
90
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""AuthGate Python SDK — one-call authentication entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import enum
|
|
6
|
+
|
|
7
|
+
from authgate._version import __version__
|
|
8
|
+
from authgate.authflow.authcode import run_auth_code_flow
|
|
9
|
+
from authgate.authflow.browser import check_browser_availability
|
|
10
|
+
from authgate.authflow.device import async_run_device_flow, run_device_flow
|
|
11
|
+
from authgate.authflow.token_source import TokenSource
|
|
12
|
+
from authgate.credstore import default_token_secure_store
|
|
13
|
+
from authgate.discovery.async_client import AsyncDiscoveryClient
|
|
14
|
+
from authgate.discovery.client import DiscoveryClient
|
|
15
|
+
from authgate.exceptions import AuthGateError
|
|
16
|
+
from authgate.oauth.async_client import AsyncOAuthClient
|
|
17
|
+
from authgate.oauth.client import OAuthClient
|
|
18
|
+
from authgate.oauth.models import Token
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FlowMode(enum.Enum):
|
|
22
|
+
"""Authentication flow selection strategy."""
|
|
23
|
+
|
|
24
|
+
AUTO = "auto"
|
|
25
|
+
BROWSER = "browser"
|
|
26
|
+
DEVICE = "device"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def authenticate(
|
|
30
|
+
authgate_url: str,
|
|
31
|
+
client_id: str,
|
|
32
|
+
*,
|
|
33
|
+
scopes: list[str] | None = None,
|
|
34
|
+
service_name: str = "authgate",
|
|
35
|
+
store_path: str = ".authgate-tokens.json",
|
|
36
|
+
local_port: int = 8088,
|
|
37
|
+
flow_mode: FlowMode = FlowMode.AUTO,
|
|
38
|
+
) -> tuple[OAuthClient, Token]:
|
|
39
|
+
"""Authenticate with an AuthGate server and return a ready-to-use client and token.
|
|
40
|
+
|
|
41
|
+
Cached tokens are reused automatically; expired tokens are refreshed.
|
|
42
|
+
When no valid token exists, the flow is determined by ``flow_mode``.
|
|
43
|
+
"""
|
|
44
|
+
if not authgate_url:
|
|
45
|
+
raise AuthGateError("authgate: authgate_url is required")
|
|
46
|
+
if not client_id:
|
|
47
|
+
raise AuthGateError("authgate: client_id is required")
|
|
48
|
+
|
|
49
|
+
_scopes = scopes or []
|
|
50
|
+
|
|
51
|
+
# 1. Discover endpoints
|
|
52
|
+
disco = DiscoveryClient(authgate_url)
|
|
53
|
+
meta = disco.fetch()
|
|
54
|
+
|
|
55
|
+
# 2. Create OAuth client
|
|
56
|
+
client = OAuthClient(client_id, meta.to_endpoints())
|
|
57
|
+
|
|
58
|
+
# 3. Set up token store and source
|
|
59
|
+
store = default_token_secure_store(service_name, store_path)
|
|
60
|
+
ts = TokenSource(client, store=store)
|
|
61
|
+
|
|
62
|
+
# 4. Return cached/refreshed token if available
|
|
63
|
+
try:
|
|
64
|
+
token = ts.token()
|
|
65
|
+
return client, token
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
# 5. No valid token — run the appropriate authentication flow
|
|
70
|
+
if flow_mode == FlowMode.BROWSER:
|
|
71
|
+
token = run_auth_code_flow(client, _scopes, local_port=local_port)
|
|
72
|
+
elif flow_mode == FlowMode.DEVICE:
|
|
73
|
+
token = run_device_flow(client, _scopes)
|
|
74
|
+
else: # AUTO
|
|
75
|
+
if check_browser_availability():
|
|
76
|
+
token = run_auth_code_flow(client, _scopes, local_port=local_port)
|
|
77
|
+
else:
|
|
78
|
+
token = run_device_flow(client, _scopes)
|
|
79
|
+
|
|
80
|
+
# 6. Persist the new token
|
|
81
|
+
ts.save_token(token)
|
|
82
|
+
|
|
83
|
+
return client, token
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def async_authenticate(
|
|
87
|
+
authgate_url: str,
|
|
88
|
+
client_id: str,
|
|
89
|
+
*,
|
|
90
|
+
scopes: list[str] | None = None,
|
|
91
|
+
service_name: str = "authgate",
|
|
92
|
+
store_path: str = ".authgate-tokens.json",
|
|
93
|
+
flow_mode: FlowMode = FlowMode.AUTO,
|
|
94
|
+
) -> tuple[AsyncOAuthClient, Token]:
|
|
95
|
+
"""Async version of authenticate().
|
|
96
|
+
|
|
97
|
+
Note: Auth Code flow is not available in async mode. Device flow is used for
|
|
98
|
+
BROWSER and AUTO modes when a browser is available.
|
|
99
|
+
"""
|
|
100
|
+
if not authgate_url:
|
|
101
|
+
raise AuthGateError("authgate: authgate_url is required")
|
|
102
|
+
if not client_id:
|
|
103
|
+
raise AuthGateError("authgate: client_id is required")
|
|
104
|
+
|
|
105
|
+
_scopes = scopes or []
|
|
106
|
+
|
|
107
|
+
# 1. Discover endpoints
|
|
108
|
+
disco = AsyncDiscoveryClient(authgate_url)
|
|
109
|
+
meta = await disco.fetch()
|
|
110
|
+
|
|
111
|
+
# 2. Create async OAuth client
|
|
112
|
+
client = AsyncOAuthClient(client_id, meta.to_endpoints())
|
|
113
|
+
|
|
114
|
+
# 3. Check stored token (sync store, run in thread)
|
|
115
|
+
store = default_token_secure_store(service_name, store_path)
|
|
116
|
+
ts = TokenSource(OAuthClient(client_id, meta.to_endpoints()), store=store)
|
|
117
|
+
try:
|
|
118
|
+
token = ts.token()
|
|
119
|
+
return client, token
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
# 4. Run device flow (always, since auth code needs sync HTTP server)
|
|
124
|
+
token = await async_run_device_flow(client, _scopes)
|
|
125
|
+
|
|
126
|
+
# 5. Persist
|
|
127
|
+
ts.save_token(token)
|
|
128
|
+
|
|
129
|
+
return client, token
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = [
|
|
133
|
+
"FlowMode",
|
|
134
|
+
"__version__",
|
|
135
|
+
"async_authenticate",
|
|
136
|
+
"authenticate",
|
|
137
|
+
]
|