epok-auth 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.
- epok_auth-0.1.0/.env.secret.example +4 -0
- epok_auth-0.1.0/.github/workflows/ci.yml +247 -0
- epok_auth-0.1.0/.github/workflows/codeql.yml +23 -0
- epok_auth-0.1.0/.gitignore +43 -0
- epok_auth-0.1.0/CHANGELOG.md +38 -0
- epok_auth-0.1.0/CONTRIBUTING.md +23 -0
- epok_auth-0.1.0/LICENSE +21 -0
- epok_auth-0.1.0/PKG-INFO +260 -0
- epok_auth-0.1.0/README.md +215 -0
- epok_auth-0.1.0/ROADMAP.md +31 -0
- epok_auth-0.1.0/SECURITY.md +33 -0
- epok_auth-0.1.0/docs/PUBLISHING.md +244 -0
- epok_auth-0.1.0/docs/SECURITY_ASSURANCE.md +45 -0
- epok_auth-0.1.0/docs/THREAT_MODEL.md +63 -0
- epok_auth-0.1.0/docs/USAGE_ES.md +737 -0
- epok_auth-0.1.0/examples/minimal/main.py +21 -0
- epok_auth-0.1.0/examples/nuxt-bff/README.md +7 -0
- epok_auth-0.1.0/examples/nuxt-bff/server/api/auth/login.post.ts +13 -0
- epok_auth-0.1.0/examples/nuxt-bff/server/api/auth/session.get.ts +6 -0
- epok_auth-0.1.0/examples/nuxt-bff/server/utils/authSession.ts +65 -0
- epok_auth-0.1.0/pyproject.toml +129 -0
- epok_auth-0.1.0/scripts/publish.py +846 -0
- epok_auth-0.1.0/scripts/publish.sh +11 -0
- epok_auth-0.1.0/src/epok_auth/__init__.py +23 -0
- epok_auth-0.1.0/src/epok_auth/cli.py +140 -0
- epok_auth-0.1.0/src/epok_auth/config.py +226 -0
- epok_auth-0.1.0/src/epok_auth/errors.py +58 -0
- epok_auth-0.1.0/src/epok_auth/fastapi/__init__.py +3 -0
- epok_auth-0.1.0/src/epok_auth/fastapi/integration.py +463 -0
- epok_auth-0.1.0/src/epok_auth/fastapi/schemas.py +145 -0
- epok_auth-0.1.0/src/epok_auth/migrate.py +40 -0
- epok_auth-0.1.0/src/epok_auth/migrations/__init__.py +1 -0
- epok_auth-0.1.0/src/epok_auth/migrations/env.py +98 -0
- epok_auth-0.1.0/src/epok_auth/migrations/script.py.mako +26 -0
- epok_auth-0.1.0/src/epok_auth/migrations/versions/0001_initial.py +240 -0
- epok_auth-0.1.0/src/epok_auth/models.py +143 -0
- epok_auth-0.1.0/src/epok_auth/passwords.py +140 -0
- epok_auth-0.1.0/src/epok_auth/postgres/__init__.py +4 -0
- epok_auth-0.1.0/src/epok_auth/postgres/store.py +288 -0
- epok_auth-0.1.0/src/epok_auth/postgres/tables.py +131 -0
- epok_auth-0.1.0/src/epok_auth/py.typed +0 -0
- epok_auth-0.1.0/src/epok_auth/service.py +810 -0
- epok_auth-0.1.0/src/epok_auth/store.py +42 -0
- epok_auth-0.1.0/src/epok_auth/testing/__init__.py +3 -0
- epok_auth-0.1.0/src/epok_auth/testing/memory.py +145 -0
- epok_auth-0.1.0/src/epok_auth/tokens.py +154 -0
- epok_auth-0.1.0/tests/__init__.py +0 -0
- epok_auth-0.1.0/tests/conftest.py +65 -0
- epok_auth-0.1.0/tests/test_cli.py +165 -0
- epok_auth-0.1.0/tests/test_config.py +166 -0
- epok_auth-0.1.0/tests/test_fastapi.py +356 -0
- epok_auth-0.1.0/tests/test_migrate.py +48 -0
- epok_auth-0.1.0/tests/test_models_and_memory.py +122 -0
- epok_auth-0.1.0/tests/test_package_metadata.py +7 -0
- epok_auth-0.1.0/tests/test_passwords.py +81 -0
- epok_auth-0.1.0/tests/test_postgres_integration.py +213 -0
- epok_auth-0.1.0/tests/test_publish_script.py +46 -0
- epok_auth-0.1.0/tests/test_service_sessions.py +338 -0
- epok_auth-0.1.0/tests/test_service_users.py +237 -0
- epok_auth-0.1.0/tests/test_tokens.py +142 -0
- epok_auth-0.1.0/uv.lock +1416 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# Copy this file to `.env.secret` and replace the placeholder locally.
|
|
2
|
+
# `.env.secret` is ignored by Git and must never be committed.
|
|
3
|
+
# The release script accepts only this variable and comments; it never executes the file as shell code.
|
|
4
|
+
UV_PUBLISH_TOKEN="pypi-REPLACE_WITH_YOUR_TOKEN"
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: epok-auth-beta-${{ github.event.pull_request.number || github.ref }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
env:
|
|
18
|
+
PYTHONUNBUFFERED: "1"
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
quality:
|
|
22
|
+
name: quality
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
timeout-minutes: 12
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
|
27
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
28
|
+
with:
|
|
29
|
+
version: "0.12.1"
|
|
30
|
+
enable-cache: true
|
|
31
|
+
- run: uv python install 3.12
|
|
32
|
+
- name: Require reproducible lockfile
|
|
33
|
+
run: |
|
|
34
|
+
test -f uv.lock
|
|
35
|
+
uv lock --check
|
|
36
|
+
- name: Validate release safeguards and CLI
|
|
37
|
+
run: |
|
|
38
|
+
git check-ignore -q .env.secret
|
|
39
|
+
! git ls-files --error-unmatch .env.secret 2>/dev/null
|
|
40
|
+
bash -n scripts/publish.sh
|
|
41
|
+
uv run scripts/publish.py --help
|
|
42
|
+
- name: Install all dependencies
|
|
43
|
+
run: uv sync --locked --all-extras --group dev --python 3.12
|
|
44
|
+
- name: Formatting
|
|
45
|
+
run: uv run ruff format --check .
|
|
46
|
+
- name: Lint and security rules
|
|
47
|
+
run: uv run ruff check .
|
|
48
|
+
- name: Strict typing
|
|
49
|
+
run: uv run pyright
|
|
50
|
+
- name: Compile all modules
|
|
51
|
+
run: uv run python -m compileall -q src tests examples/minimal scripts/publish.py
|
|
52
|
+
- name: Dependency vulnerability audit
|
|
53
|
+
run: uv run pip-audit --local --progress-spinner off
|
|
54
|
+
|
|
55
|
+
tests:
|
|
56
|
+
name: tests-py${{ matrix.python-version }}
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
timeout-minutes: 15
|
|
59
|
+
strategy:
|
|
60
|
+
fail-fast: false
|
|
61
|
+
matrix:
|
|
62
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
|
65
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
66
|
+
with:
|
|
67
|
+
version: "0.12.1"
|
|
68
|
+
enable-cache: true
|
|
69
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
70
|
+
- run: uv sync --locked --all-extras --group dev --python ${{ matrix.python-version }}
|
|
71
|
+
- name: Unit, HTTP and adversarial suite
|
|
72
|
+
run: uv run --python ${{ matrix.python-version }} pytest -m "not integration" -q
|
|
73
|
+
|
|
74
|
+
postgres:
|
|
75
|
+
name: postgres-17
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
timeout-minutes: 20
|
|
78
|
+
env:
|
|
79
|
+
TEST_DATABASE_URL: postgresql+psycopg://epok_auth:epok_auth@127.0.0.1:5432/epok_auth_test
|
|
80
|
+
services:
|
|
81
|
+
postgres:
|
|
82
|
+
image: postgres:17-alpine
|
|
83
|
+
env:
|
|
84
|
+
POSTGRES_USER: epok_auth
|
|
85
|
+
POSTGRES_PASSWORD: epok_auth
|
|
86
|
+
POSTGRES_DB: epok_auth_test
|
|
87
|
+
ports: ["5432:5432"]
|
|
88
|
+
options: >-
|
|
89
|
+
--health-cmd "pg_isready -U epok_auth -d epok_auth_test"
|
|
90
|
+
--health-interval 2s
|
|
91
|
+
--health-timeout 3s
|
|
92
|
+
--health-retries 30
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
|
95
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
96
|
+
with:
|
|
97
|
+
version: "0.12.1"
|
|
98
|
+
enable-cache: true
|
|
99
|
+
- run: uv python install 3.12
|
|
100
|
+
- run: uv sync --locked --all-extras --group dev --python 3.12
|
|
101
|
+
- name: Migrate from an empty database
|
|
102
|
+
run: |
|
|
103
|
+
uv run python - <<'PY'
|
|
104
|
+
import os
|
|
105
|
+
from epok_auth.migrate import upgrade_database
|
|
106
|
+
|
|
107
|
+
upgrade_database(os.environ["TEST_DATABASE_URL"])
|
|
108
|
+
PY
|
|
109
|
+
- name: Assert metadata has no migration drift
|
|
110
|
+
run: |
|
|
111
|
+
uv run python - <<'PY'
|
|
112
|
+
import os
|
|
113
|
+
from epok_auth.migrate import check_database
|
|
114
|
+
|
|
115
|
+
check_database(os.environ["TEST_DATABASE_URL"])
|
|
116
|
+
PY
|
|
117
|
+
- name: PostgreSQL integration and concurrency suite
|
|
118
|
+
run: uv run pytest -m integration -q
|
|
119
|
+
|
|
120
|
+
coverage:
|
|
121
|
+
name: coverage
|
|
122
|
+
runs-on: ubuntu-latest
|
|
123
|
+
timeout-minutes: 20
|
|
124
|
+
env:
|
|
125
|
+
TEST_DATABASE_URL: postgresql+psycopg://epok_auth:epok_auth@127.0.0.1:5432/epok_auth_test
|
|
126
|
+
services:
|
|
127
|
+
postgres:
|
|
128
|
+
image: postgres:17-alpine
|
|
129
|
+
env:
|
|
130
|
+
POSTGRES_USER: epok_auth
|
|
131
|
+
POSTGRES_PASSWORD: epok_auth
|
|
132
|
+
POSTGRES_DB: epok_auth_test
|
|
133
|
+
ports: ["5432:5432"]
|
|
134
|
+
options: >-
|
|
135
|
+
--health-cmd "pg_isready -U epok_auth -d epok_auth_test"
|
|
136
|
+
--health-interval 2s
|
|
137
|
+
--health-timeout 3s
|
|
138
|
+
--health-retries 30
|
|
139
|
+
steps:
|
|
140
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
|
141
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
142
|
+
with:
|
|
143
|
+
version: "0.12.1"
|
|
144
|
+
enable-cache: true
|
|
145
|
+
- run: uv python install 3.12
|
|
146
|
+
- run: uv sync --locked --all-extras --group dev --python 3.12
|
|
147
|
+
- name: Migrate from an empty database
|
|
148
|
+
run: |
|
|
149
|
+
uv run python - <<'PY'
|
|
150
|
+
import os
|
|
151
|
+
from epok_auth.migrate import upgrade_database
|
|
152
|
+
|
|
153
|
+
upgrade_database(os.environ["TEST_DATABASE_URL"])
|
|
154
|
+
PY
|
|
155
|
+
- name: Enforce branch coverage across unit and PostgreSQL paths
|
|
156
|
+
run: >-
|
|
157
|
+
uv run pytest
|
|
158
|
+
--cov=epok_auth
|
|
159
|
+
--cov-branch
|
|
160
|
+
--cov-report=term-missing
|
|
161
|
+
--cov-report=xml
|
|
162
|
+
--cov-fail-under=90
|
|
163
|
+
|
|
164
|
+
package:
|
|
165
|
+
name: package
|
|
166
|
+
runs-on: ubuntu-latest
|
|
167
|
+
timeout-minutes: 18
|
|
168
|
+
steps:
|
|
169
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
|
170
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
171
|
+
with:
|
|
172
|
+
version: "0.12.1"
|
|
173
|
+
enable-cache: true
|
|
174
|
+
- run: uv python install 3.12
|
|
175
|
+
- name: Build wheel and source distribution without local sources
|
|
176
|
+
run: uv build --no-sources
|
|
177
|
+
- name: Verify packaged files
|
|
178
|
+
shell: bash
|
|
179
|
+
run: |
|
|
180
|
+
set -euo pipefail
|
|
181
|
+
wheel="$(find dist -maxdepth 1 -name '*.whl' -print -quit)"
|
|
182
|
+
sdist="$(find dist -maxdepth 1 -name '*.tar.gz' -print -quit)"
|
|
183
|
+
test -n "$wheel"
|
|
184
|
+
test -n "$sdist"
|
|
185
|
+
test "$(find dist -maxdepth 1 -name '*.whl' | wc -l)" -eq 1
|
|
186
|
+
test "$(find dist -maxdepth 1 -name '*.tar.gz' | wc -l)" -eq 1
|
|
187
|
+
python -m zipfile -l "$wheel" | grep 'epok_auth/migrations/versions/0001_initial.py'
|
|
188
|
+
python -m zipfile -l "$wheel" | grep 'epok_auth/py.typed'
|
|
189
|
+
tar -tzf "$sdist" | grep 'src/epok_auth/migrations/versions/0001_initial.py'
|
|
190
|
+
tar -tzf "$sdist" | grep 'src/epok_auth/py.typed'
|
|
191
|
+
- name: Isolated wheel and sdist smoke tests
|
|
192
|
+
shell: bash
|
|
193
|
+
run: |
|
|
194
|
+
set -euo pipefail
|
|
195
|
+
expected_version="$(uv version --short)"
|
|
196
|
+
wheel="$(find dist -maxdepth 1 -name '*.whl' -print -quit)"
|
|
197
|
+
sdist="$(find dist -maxdepth 1 -name '*.tar.gz' -print -quit)"
|
|
198
|
+
|
|
199
|
+
smoke_test() {
|
|
200
|
+
artifact="$1"
|
|
201
|
+
environment="$2"
|
|
202
|
+
uv venv "$environment" --python 3.12
|
|
203
|
+
uv pip install --python "$environment/bin/python" "$artifact"
|
|
204
|
+
EPOK_AUTH_EXPECTED_VERSION="$expected_version" \
|
|
205
|
+
"$environment/bin/python" - <<'PY'
|
|
206
|
+
import os
|
|
207
|
+
from importlib.metadata import version
|
|
208
|
+
|
|
209
|
+
import epok_auth
|
|
210
|
+
from epok_auth import AuthSettings, EpokAuth
|
|
211
|
+
|
|
212
|
+
expected = os.environ["EPOK_AUTH_EXPECTED_VERSION"]
|
|
213
|
+
assert version("epok-auth") == expected
|
|
214
|
+
assert epok_auth.__version__ == expected
|
|
215
|
+
assert AuthSettings is not None
|
|
216
|
+
assert EpokAuth is not None
|
|
217
|
+
PY
|
|
218
|
+
"$environment/bin/epok-auth" --help >/dev/null
|
|
219
|
+
"$environment/bin/epok-auth" generate-secret --bytes 32 | grep -E '.{40,}'
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
smoke_test "$wheel" /tmp/epok-auth-wheel-smoke
|
|
223
|
+
smoke_test "$sdist" /tmp/epok-auth-sdist-smoke
|
|
224
|
+
|
|
225
|
+
merge-gate:
|
|
226
|
+
name: merge-gate
|
|
227
|
+
if: always()
|
|
228
|
+
needs: [quality, tests, postgres, coverage, package]
|
|
229
|
+
runs-on: ubuntu-latest
|
|
230
|
+
timeout-minutes: 5
|
|
231
|
+
steps:
|
|
232
|
+
- name: Require every beta gate to pass
|
|
233
|
+
shell: bash
|
|
234
|
+
env:
|
|
235
|
+
QUALITY: ${{ needs.quality.result }}
|
|
236
|
+
TESTS: ${{ needs.tests.result }}
|
|
237
|
+
POSTGRES: ${{ needs.postgres.result }}
|
|
238
|
+
COVERAGE: ${{ needs.coverage.result }}
|
|
239
|
+
PACKAGE: ${{ needs.package.result }}
|
|
240
|
+
run: |
|
|
241
|
+
set -euo pipefail
|
|
242
|
+
printf '%-12s %s\n' quality "$QUALITY" tests "$TESTS" postgres "$POSTGRES" coverage "$COVERAGE" package "$PACKAGE"
|
|
243
|
+
test "$QUALITY" = success
|
|
244
|
+
test "$TESTS" = success
|
|
245
|
+
test "$POSTGRES" = success
|
|
246
|
+
test "$COVERAGE" = success
|
|
247
|
+
test "$PACKAGE" = success
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: CodeQL
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
security-events: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
analyze:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 15
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
|
19
|
+
- uses: github/codeql-action/init@0daab03d71ff584ef619d027a3fd9146679c5d84
|
|
20
|
+
with:
|
|
21
|
+
languages: python
|
|
22
|
+
queries: security-extended
|
|
23
|
+
- uses: github/codeql-action/analyze@0daab03d71ff584ef619d027a3fd9146679c5d84
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Local environments and secrets
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
!.env.secret.example
|
|
6
|
+
.envrc
|
|
7
|
+
.secrets/
|
|
8
|
+
*credentials*.json
|
|
9
|
+
*service-account*.json
|
|
10
|
+
application_default_credentials.json
|
|
11
|
+
adc.json
|
|
12
|
+
token*.json
|
|
13
|
+
*.pem
|
|
14
|
+
*.key
|
|
15
|
+
*.p12
|
|
16
|
+
*.pfx
|
|
17
|
+
*.jks
|
|
18
|
+
*.keystore
|
|
19
|
+
|
|
20
|
+
# Python and tooling
|
|
21
|
+
.venv/
|
|
22
|
+
__pycache__/
|
|
23
|
+
*.py[cod]
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
.pyright/
|
|
27
|
+
.coverage
|
|
28
|
+
coverage.xml
|
|
29
|
+
htmlcov/
|
|
30
|
+
|
|
31
|
+
# Build and local data
|
|
32
|
+
dist/
|
|
33
|
+
build/
|
|
34
|
+
*.egg-info/
|
|
35
|
+
*.sqlite
|
|
36
|
+
*.sqlite3
|
|
37
|
+
*.db
|
|
38
|
+
*.log
|
|
39
|
+
|
|
40
|
+
# Operating systems and editors
|
|
41
|
+
.DS_Store
|
|
42
|
+
.idea/
|
|
43
|
+
.vscode/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- replace the duplicated shell release implementation with a Rich/Typer Python orchestrator;
|
|
6
|
+
- run the release CLI through isolated PEP 723 dependencies;
|
|
7
|
+
- validate Python 3.12, 3.13 and 3.14 locally without replacing the project `.venv`;
|
|
8
|
+
- execute PostgreSQL 17 migrations, drift, integration, concurrency and coverage locally;
|
|
9
|
+
- build and smoke-test both wheel and source distribution;
|
|
10
|
+
- publish, create the annotated tag and verify the public PyPI installation in one command;
|
|
11
|
+
- add regression tests for `.env.secret` parsing and Docker port discovery;
|
|
12
|
+
- retain `scripts/publish.sh` as a compatibility alias.
|
|
13
|
+
|
|
14
|
+
## 0.1.0b1 — 2026-08-04
|
|
15
|
+
|
|
16
|
+
First public beta and production-candidate foundation for the Colors integration:
|
|
17
|
+
|
|
18
|
+
- local user administration;
|
|
19
|
+
- Argon2id credentials with dummy verification and rehash support;
|
|
20
|
+
- strict short-lived access JWTs;
|
|
21
|
+
- opaque rotating refresh sessions;
|
|
22
|
+
- session families, reuse detection and immediate revocation;
|
|
23
|
+
- idle and absolute session deadlines;
|
|
24
|
+
- CSRF correlation and Origin protection;
|
|
25
|
+
- PostgreSQL-authoritative sessions and security events;
|
|
26
|
+
- packaged Alembic migrations with drift checking;
|
|
27
|
+
- FastAPI routers, dependencies and administrative endpoints;
|
|
28
|
+
- operational CLI and Nuxt BFF reference;
|
|
29
|
+
- reproducible `uv.lock` and isolated package installation;
|
|
30
|
+
- single-source versioning through `pyproject.toml`, `uv version` and `importlib.metadata`;
|
|
31
|
+
- guarded local publication through `uv build --no-sources` and `uv publish`;
|
|
32
|
+
- ignored local PyPI credentials with repository safeguards;
|
|
33
|
+
- CodeQL, dependency audit, strict typing and Ruff security rules;
|
|
34
|
+
- branch-aware coverage above 90%;
|
|
35
|
+
- Python 3.12, 3.13 and 3.14 validation;
|
|
36
|
+
- PostgreSQL 17 integration and concurrency validation.
|
|
37
|
+
|
|
38
|
+
The standalone beta is published on PyPI and tagged as `v0.1.0b1`. Colors backend/frontend parity, deployment configuration and application authorization remain required before product rollout.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
All changes enter through a pull request. Direct pushes to `main` are not part of the supported workflow.
|
|
4
|
+
|
|
5
|
+
Authentication, cryptography, cookie, CSRF, migration and concurrency changes require:
|
|
6
|
+
|
|
7
|
+
1. a stated security invariant;
|
|
8
|
+
2. a functional test;
|
|
9
|
+
3. an adversarial or regression test;
|
|
10
|
+
4. PostgreSQL coverage when persistence semantics change;
|
|
11
|
+
5. an update to the assurance manifest when the threat model changes.
|
|
12
|
+
|
|
13
|
+
Run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv sync --all-extras --group dev
|
|
17
|
+
uv run ruff format .
|
|
18
|
+
uv run ruff check .
|
|
19
|
+
uv run pyright
|
|
20
|
+
uv run pytest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
No contribution may introduce custom cryptographic algorithms or log credential material.
|
epok_auth-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EPOK
|
|
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.
|
epok_auth-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epok-auth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI-first authentication with PostgreSQL-authoritative, revocable sessions.
|
|
5
|
+
Project-URL: Documentation, https://github.com/epok200/epok_auth/blob/main/docs/USAGE_ES.md
|
|
6
|
+
Project-URL: Changelog, https://github.com/epok200/epok_auth/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Repository, https://github.com/epok200/epok_auth
|
|
8
|
+
Project-URL: Issues, https://github.com/epok200/epok_auth/issues
|
|
9
|
+
Project-URL: Security, https://github.com/epok200/epok_auth/security/policy
|
|
10
|
+
Author: EPOK
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: authentication,fastapi,jwt,postgresql,security,sessions
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Security
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.12
|
|
27
|
+
Requires-Dist: email-validator<3,>=2.2
|
|
28
|
+
Requires-Dist: fastapi<1,>=0.128
|
|
29
|
+
Requires-Dist: pwdlib[argon2]<0.4,>=0.3
|
|
30
|
+
Requires-Dist: pydantic-settings<3,>=2.11
|
|
31
|
+
Requires-Dist: pydantic<3,>=2.12
|
|
32
|
+
Requires-Dist: pyjwt<3,>=2.10
|
|
33
|
+
Requires-Dist: typer<1,>=0.20
|
|
34
|
+
Provides-Extra: all
|
|
35
|
+
Requires-Dist: alembic<2,>=1.17; extra == 'all'
|
|
36
|
+
Requires-Dist: greenlet<4,>=3.0; extra == 'all'
|
|
37
|
+
Requires-Dist: psycopg[binary,pool]<4,>=3.2; extra == 'all'
|
|
38
|
+
Requires-Dist: sqlalchemy<2.1,>=2.0.45; extra == 'all'
|
|
39
|
+
Provides-Extra: postgres
|
|
40
|
+
Requires-Dist: alembic<2,>=1.17; extra == 'postgres'
|
|
41
|
+
Requires-Dist: greenlet<4,>=3.0; extra == 'postgres'
|
|
42
|
+
Requires-Dist: psycopg[binary,pool]<4,>=3.2; extra == 'postgres'
|
|
43
|
+
Requires-Dist: sqlalchemy<2.1,>=2.0.45; extra == 'postgres'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# epok-auth
|
|
47
|
+
|
|
48
|
+
**FastAPI-first authentication with PostgreSQL-authoritative, revocable sessions.**
|
|
49
|
+
|
|
50
|
+
`epok-auth` is designed for private B2B web applications that need secure local accounts without rebuilding password handling, session rotation, revocation, CSRF protection, administration, and FastAPI dependencies for every product.
|
|
51
|
+
|
|
52
|
+
> **Status:** `0.1.0b1` public beta on PyPI. The standalone library gate is green. Colors integration and application-level parity remain required before using this beta in that product. Public APIs may still change before `1.0`.
|
|
53
|
+
>
|
|
54
|
+
> **Practical testing:** see the Spanish step-by-step guide in [`docs/USAGE_ES.md`](docs/USAGE_ES.md).
|
|
55
|
+
|
|
56
|
+
## Validated beta gate
|
|
57
|
+
|
|
58
|
+
The clean beta tree is continuously validated by GitHub Actions. The current candidate has passed:
|
|
59
|
+
|
|
60
|
+
| Gate | Evidence |
|
|
61
|
+
|---|---|
|
|
62
|
+
| Functional and adversarial tests | 101/101 passing |
|
|
63
|
+
| Branch coverage | 94.80% |
|
|
64
|
+
| Python compatibility | 3.12, 3.13 and 3.14 |
|
|
65
|
+
| PostgreSQL | PostgreSQL 17 migration, zero Alembic drift, integration and concurrency tests |
|
|
66
|
+
| Static quality | Ruff formatting/lint/security rules and Pyright strict on production source |
|
|
67
|
+
| Dependencies | Reproducible `uv.lock` and `pip-audit` |
|
|
68
|
+
| Distribution | Wheel and sdist build, packaged migrations, `py.typed`, isolated install and CLI smoke test |
|
|
69
|
+
| Code scanning | CodeQL `security-extended` |
|
|
70
|
+
|
|
71
|
+
The repository does not claim that vulnerabilities are impossible. The green gate establishes reproducible evidence for the defined beta threat model and invariants.
|
|
72
|
+
|
|
73
|
+
## What the beta includes
|
|
74
|
+
|
|
75
|
+
- Argon2id password hashing through `pwdlib`, with rehash support and dummy verification;
|
|
76
|
+
- local users, active/disabled state, roles, scopes, administrative provisioning and reset;
|
|
77
|
+
- short-lived access JWTs with strict issuer, audience, algorithm, type and time validation;
|
|
78
|
+
- opaque refresh credentials stored only as SHA-256 hashes;
|
|
79
|
+
- refresh rotation, session families and reuse detection;
|
|
80
|
+
- immediate access revocation through authoritative PostgreSQL session state;
|
|
81
|
+
- inactivity and absolute session deadlines;
|
|
82
|
+
- secure cookies, CSRF correlation and strict Origin allowlists;
|
|
83
|
+
- account lockout, uniform login failures and security-event persistence;
|
|
84
|
+
- plug-and-play FastAPI routers and dependencies;
|
|
85
|
+
- packaged Alembic migrations and an operational CLI;
|
|
86
|
+
- a Nuxt/Nitro BFF reference where Vue never receives access or refresh tokens.
|
|
87
|
+
|
|
88
|
+
Google OIDC, TOTP/MFA, passkeys, Redis coordination, multi-tenancy and service-to-service authentication remain outside this beta. See [ROADMAP.md](ROADMAP.md).
|
|
89
|
+
|
|
90
|
+
## Installation
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
uv add "epok-auth[postgres]==0.1.0b1"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Generate a secret and configure the application:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
uv run epok-auth generate-secret
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```dotenv
|
|
103
|
+
EPOK_AUTH_ENVIRONMENT=production
|
|
104
|
+
EPOK_AUTH_DATABASE_URL=postgresql://colors:password@postgres/colors
|
|
105
|
+
EPOK_AUTH_JWT_SECRET=<generated-secret>
|
|
106
|
+
EPOK_AUTH_ISSUER=colors-auth
|
|
107
|
+
EPOK_AUTH_AUDIENCE=colors-api
|
|
108
|
+
EPOK_AUTH_TRUSTED_ORIGINS=https://colors.example.com
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Production configuration is **fail-closed**: weak secrets, insecure cookies, generic issuer/audience values, missing PostgreSQL, and ambiguous origins prevent startup.
|
|
112
|
+
|
|
113
|
+
## Publication
|
|
114
|
+
|
|
115
|
+
The version has one source of truth in `pyproject.toml` and is exposed at runtime through `importlib.metadata`:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
uv version --short
|
|
119
|
+
uv version --bump beta
|
|
120
|
+
uv version --bump stable
|
|
121
|
+
uv version --bump patch
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`uv version --short` prints the current project version, not the installed version of `uv`.
|
|
125
|
+
|
|
126
|
+
Local PyPI credentials belong in an ignored `.env.secret` file. The complete release pipeline is a single Python command:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
cp .env.secret.example .env.secret
|
|
130
|
+
uv run scripts/publish.py --validate-only
|
|
131
|
+
uv run scripts/publish.py --dry-run
|
|
132
|
+
uv run scripts/publish.py
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The normal command validates Python 3.12–3.14, launches disposable PostgreSQL 17, runs migrations, drift checks, integration, concurrency and coverage, builds and installs wheel/sdist, simulates the upload, publishes after an exact-version confirmation, pushes the tag and verifies the public PyPI installation.
|
|
136
|
+
|
|
137
|
+
The script uses inline PEP 723 dependencies and `uv run --isolated`, so it does not replace the developer's project `.venv`. The legacy `bash scripts/publish.sh` command remains as a thin alias. See [docs/PUBLISHING.md](docs/PUBLISHING.md) for the complete procedure.
|
|
138
|
+
|
|
139
|
+
## Database and initial administrator
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
uv run epok-auth check-config
|
|
143
|
+
uv run epok-auth upgrade-db
|
|
144
|
+
uv run epok-auth check-db
|
|
145
|
+
uv run epok-auth create-admin
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The first administrator is serialized transactionally. A second initial-admin creation attempt fails rather than racing.
|
|
149
|
+
|
|
150
|
+
## FastAPI integration
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from fastapi import Depends, FastAPI
|
|
154
|
+
|
|
155
|
+
from epok_auth import AuthSettings, EpokAuth, Principal
|
|
156
|
+
|
|
157
|
+
settings = AuthSettings()
|
|
158
|
+
auth = EpokAuth.postgres(settings=settings)
|
|
159
|
+
|
|
160
|
+
app = FastAPI()
|
|
161
|
+
auth.install(
|
|
162
|
+
app,
|
|
163
|
+
prefix="/api/v1/auth",
|
|
164
|
+
include_admin=True,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
catalog = auth.protected_router(prefix="/api/v1/catalog")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@catalog.get("")
|
|
171
|
+
async def get_catalog(
|
|
172
|
+
principal: Principal = Depends(auth.authenticated),
|
|
173
|
+
) -> dict[str, str]:
|
|
174
|
+
return {"viewer": principal.email}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@catalog.post("")
|
|
178
|
+
async def update_catalog(
|
|
179
|
+
principal: Principal = Depends(auth.require_scopes("catalog:write")),
|
|
180
|
+
) -> dict[str, str]:
|
|
181
|
+
return {"editor": principal.email}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
app.include_router(catalog)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`auth.install()` exposes:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
POST /api/v1/auth/login
|
|
191
|
+
POST /api/v1/auth/refresh
|
|
192
|
+
POST /api/v1/auth/logout
|
|
193
|
+
POST /api/v1/auth/change-password
|
|
194
|
+
GET /api/v1/auth/me
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
With `include_admin=True` it also exposes protected user administration under `/api/v1/auth/users`.
|
|
198
|
+
|
|
199
|
+
## Application boundary
|
|
200
|
+
|
|
201
|
+
`epok-auth` owns authentication capabilities:
|
|
202
|
+
|
|
203
|
+
- credentials and account state;
|
|
204
|
+
- sessions, rotation and revocation;
|
|
205
|
+
- generic roles/scopes;
|
|
206
|
+
- browser transport protections;
|
|
207
|
+
- authentication audit events.
|
|
208
|
+
|
|
209
|
+
The consuming product still owns:
|
|
210
|
+
|
|
211
|
+
- tenants and memberships;
|
|
212
|
+
- domain permissions;
|
|
213
|
+
- resource-level authorization;
|
|
214
|
+
- business profiles and data;
|
|
215
|
+
- frontend UI and infrastructure.
|
|
216
|
+
|
|
217
|
+
A role named `editor` has no meaning until Colors decides what an editor can do.
|
|
218
|
+
|
|
219
|
+
## Nuxt BFF
|
|
220
|
+
|
|
221
|
+
The BFF is **not implemented inside the Python library**. The repository includes a reference integration under [`examples/nuxt-bff`](examples/nuxt-bff) that demonstrates this boundary:
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
Browser ── HttpOnly opaque session cookie ──> Nuxt/Nitro
|
|
225
|
+
Nuxt/Nitro ── protected access/refresh ──> FastAPI + epok-auth
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Vue receives only safe user/session state. Access and refresh credentials remain server-side.
|
|
229
|
+
|
|
230
|
+
## Documentation
|
|
231
|
+
|
|
232
|
+
- [Minimal usage and test guide in Spanish](docs/USAGE_ES.md)
|
|
233
|
+
- [Publishing and versioning](docs/PUBLISHING.md)
|
|
234
|
+
- [Threat model](docs/THREAT_MODEL.md)
|
|
235
|
+
- [Security assurance](docs/SECURITY_ASSURANCE.md)
|
|
236
|
+
- [Security policy](SECURITY.md)
|
|
237
|
+
|
|
238
|
+
## Security model
|
|
239
|
+
|
|
240
|
+
The beta is designed around these invariants:
|
|
241
|
+
|
|
242
|
+
- knowledge of the source code does not grant access;
|
|
243
|
+
- PostgreSQL is the authority for session validity;
|
|
244
|
+
- refresh credentials are one-time, opaque and hashed at rest;
|
|
245
|
+
- replay revokes the whole session family;
|
|
246
|
+
- changing a password, disabling or locking a user revokes sessions;
|
|
247
|
+
- unsafe production configuration fails before serving traffic;
|
|
248
|
+
- authentication errors do not echo secrets or distinguish unknown users.
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
uv sync --locked --all-extras --group dev
|
|
254
|
+
uv run ruff format --check .
|
|
255
|
+
uv run ruff check .
|
|
256
|
+
uv run pyright
|
|
257
|
+
uv run pytest
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Pull requests must pass the GitHub Actions `CI / merge-gate`, including PostgreSQL 17, Python 3.12–3.14, branch coverage, dependency auditing, packaging and isolated installation. `CodeQL` must also pass.
|