Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
26fea7f
Initial implementation
sevanteri Jan 9, 2025
934e56b
Add more configs to the registry.
longnguyendr Jan 14, 2025
7befdf3
Define Protocol interfaces.
longnguyendr Jan 14, 2025
3faa61b
Update authentication:
longnguyendr Jan 16, 2025
be79cc9
Update authentication:
longnguyendr Jan 20, 2025
5879077
Add route_prefix.
longnguyendr Jan 21, 2025
f640626
Update authentication module:
longnguyendr Jan 22, 2025
fe2b457
Add test suites.
longnguyendr Jan 23, 2025
0ed8623
Update pytest configuration:
longnguyendr Jan 24, 2025
601f778
Update the tests's README
longnguyendr Jan 24, 2025
e814e9e
Update authentication test:
longnguyendr Jan 24, 2025
2dd4ad4
Update tet.security.authentication:
longnguyendr Jan 24, 2025
bdfd7d8
update test's README
longnguyendr Jan 28, 2025
e0b6c1d
Refactor authentication: Rename callback interface, add ACL-based per…
longnguyendr Jan 28, 2025
2a75770
Update conftest:
longnguyendr Jan 28, 2025
107776a
Update tests:
longnguyendr Jan 28, 2025
c0e4122
Remove un-used imports, and unnecessary comments
longnguyendr Jan 28, 2025
2e05d33
Some update for tests:
longnguyendr Jan 29, 2025
f9ff838
Add ruff and pre-commit.
longnguyendr Jan 29, 2025
8fa749b
Add rules for ruff, and run formatting for all files.
longnguyendr Jan 29, 2025
845a0ed
Minor update:
longnguyendr Jan 30, 2025
aebc7fa
Authentication: Rename methods and params
longnguyendr Jan 30, 2025
d3c2b0c
Update authentication:
longnguyendr Jan 31, 2025
9e4077d
Rename tet_configure_token_authentication.
longnguyendr Jan 31, 2025
0a6b42f
Fix missing psycopg2 dependency error. Sqlalchemy required.
longnguyendr Jan 31, 2025
4ac1fe5
Update CI:
longnguyendr Feb 4, 2025
d886967
Make sure return type work in python 3.9+.
longnguyendr Feb 4, 2025
ad8c1ad
Update return type which also supported by python version 3.9
longnguyendr Feb 4, 2025
15e9a47
Update authentication:
longnguyendr Feb 12, 2025
654246b
Update authentication:
longnguyendr Feb 13, 2025
549f4dd
remove unsafe cookie settings
longnguyendr Feb 14, 2025
aca9248
Update routes pattern.
longnguyendr Feb 18, 2025
c271696
Update default refresh token route.
longnguyendr Feb 18, 2025
a375d85
refresh token view: return json
longnguyendr Feb 18, 2025
cf23740
Allow refresh token to be sent across auth routes.
longnguyendr Mar 4, 2025
20b2a4f
Update authentication:
longnguyendr Mar 20, 2025
7a42107
Update authentication:
longnguyendr Mar 20, 2025
be1b24b
Register TetMultiFactorAuthenticationService
longnguyendr Mar 20, 2025
d476144
Update the request route_url
longnguyendr Mar 21, 2025
59a75df
Add early return on HTTPFound in cookie_login to skip setting cookie.
longnguyendr Mar 24, 2025
f0460b4
Update mfa feature:
longnguyendr Mar 24, 2025
deae6a4
Update mfa feature:
longnguyendr Mar 24, 2025
d35a8c6
Update mfa feature:
longnguyendr Mar 24, 2025
4f4d3e4
Fix login flow: correctly return mfa_required response payload instea…
longnguyendr Mar 24, 2025
0559dbd
Update mfa feature:
longnguyendr Mar 24, 2025
8f3354d
Update mfa authentication:
longnguyendr Mar 24, 2025
025c9f1
Update get_or_create_method should return mfa method data
longnguyendr Mar 24, 2025
3ab692a
Update verify_mfa:
longnguyendr Mar 24, 2025
21e63c3
Update MFA service:
longnguyendr Mar 27, 2025
fe79204
Update get_active_methods_by_user_id:
longnguyendr Mar 27, 2025
143fa4b
Update filter condition of get_active_methods_by_user_id.
longnguyendr Mar 27, 2025
8cff0fc
Update the MFA method's verified state if valid.
longnguyendr Mar 27, 2025
f8dffb8
Remove long term token from the cookie if invalid.
longnguyendr Apr 1, 2025
37f1e84
Update security module:
longnguyendr Apr 15, 2025
2f792b7
Revert "Update security module:"
longnguyendr Apr 15, 2025
17f9955
Update security module:
longnguyendr Apr 15, 2025
924bb52
Fix broken tests
longnguyendr Apr 15, 2025
c7df0de
Update tests:
longnguyendr Apr 15, 2025
d4a6ab7
Passing string in the query filter instead of Enum type
longnguyendr Apr 17, 2025
40187e9
Using string instead of method type when instantiate new mfa method.
longnguyendr Apr 17, 2025
e38d197
Switch back to enum type instead of string value when instantiate the…
longnguyendr Apr 17, 2025
66dbbb0
PostgreSQL enum labels were uppercase (e.g. 'TOTP') due to default En…
longnguyendr Apr 17, 2025
13cf17a
Update security module:
longnguyendr Apr 17, 2025
f14e999
Set the cookie value from the response header if the cookie_attribute…
longnguyendr Apr 17, 2025
5bab213
Set cookie_attributes value only if it exist
longnguyendr Apr 17, 2025
67b0aaa
Update security module:
longnguyendr Apr 22, 2025
e91189a
Update security:
longnguyendr Apr 23, 2025
0a44298
Update security module:
longnguyendr Apr 23, 2025
b1aac81
Update security:
longnguyendr Apr 23, 2025
db22a5c
Refactor MFA method retrieval to improve filter logic
longnguyendr Apr 23, 2025
c1bd952
Rename _set_tokens to _set_session_tokens for clarity
longnguyendr Apr 24, 2025
29fc0c3
Refactor MFA method naming and behaviour for clarity and consistency
longnguyendr Apr 24, 2025
6a714e6
Update _verify_totp_by_user_id:
longnguyendr Apr 24, 2025
d0ce7eb
Update _verify_totp_by_user_id:
longnguyendr Apr 24, 2025
6cfdb11
Rename the MFA challenge route using a hyphen instead of an underscore
longnguyendr Apr 24, 2025
0fe6484
Rename the get access token, and refresh token routes using a hyphen …
longnguyendr Apr 24, 2025
c4dc172
Update test:
longnguyendr Apr 24, 2025
35d9aea
Refine MFA method activation logic for TOTP verification
longnguyendr Apr 24, 2025
ba7391b
Update MultiFactorAuthenticationMethodMixin:
longnguyendr Apr 24, 2025
b68a550
Update security module:
longnguyendr May 7, 2025
b06e0b4
break everything
longnguyendr May 7, 2025
716ffe2
Revert "break everything"
longnguyendr May 7, 2025
5f492b8
Add try/catch block for the _verify_totp_by_user_id method
longnguyendr May 16, 2025
fc77681
Add api docs for the authentication module
longnguyendr Jun 24, 2025
901fdab
refactor(auth): rework authentication and MFA service classes, simpli…
longnguyendr Jun 24, 2025
eabcf9f
test(auth): update authentication tests for new login and token endpo…
longnguyendr Jun 24, 2025
92c4647
feat(auth): add password change endpoint and enforce password security
longnguyendr Jun 24, 2025
c81c7d0
Add full MFA management endpoints, TOTP setup with QR, and token revo…
longnguyendr Jun 25, 2025
b0a67c8
Make logout API public.
longnguyendr Jun 26, 2025
e29b48f
Update the return data of change_password
longnguyendr Jun 26, 2025
3caac54
Add a proper error handling for the change_password view
longnguyendr Jun 26, 2025
b1da82a
feat: add structured auth events and registry notifications
longnguyendr Jul 1, 2025
06ea606
Remove redundant LoginSuccessEvent notification and fix login return …
longnguyendr Jul 2, 2025
cc1b711
Add CreateTotpMethodSuccessEvent and improve error handling in authen…
longnguyendr Jul 2, 2025
4aeb66b
Add test for authentication events:
longnguyendr Jul 2, 2025
40ae528
Refactor security event notifications to use explicit security_events…
longnguyendr Jul 3, 2025
d295c50
refactor(authn-events): unify security events under new Authn* classes
longnguyendr Jul 3, 2025
275a29e
tests(security): include user_id in login event tests
longnguyendr Jul 3, 2025
cb97e01
Refactor login events to use user_identity instead of user_id
longnguyendr Jul 4, 2025
38400c4
refactor: adopt industry standard Bearer scheme for JWT extraction
longnguyendr Jul 4, 2025
a1149fc
Refactor auth headers: use 'Authorization' for access tokens, remove …
longnguyendr Jul 4, 2025
f4f9be5
Refactor: re-raise HTTPException directly without binding to variable
longnguyendr Jul 7, 2025
3472443
Use 'Authorization' header with Bearer tokens in authentication tests
longnguyendr Jul 7, 2025
12a5c2d
Refactor login flow and error handling in authentication service
longnguyendr Jul 11, 2025
704bd0c
Refactor authentication tests and login callback
longnguyendr Jul 11, 2025
ec87ac7
Update tests:
longnguyendr Jul 11, 2025
f51b1aa
Update tests utils:
longnguyendr Jul 11, 2025
e9a111c
docs: add TODO for JWT auth fixes needed before merge
ztane Feb 15, 2026
183a1e9
docs: add TODO item — return refresh token in response body too
ztane Feb 15, 2026
a7214c4
docs: clarify magic number note — prefix handling is actually correct
ztane Feb 15, 2026
f41c578
refactor: split security monolith into modules, fix auth bugs
ztane Feb 15, 2026
b4ea409
fix(ci): pin setuptools<82 for pkg_resources compatibility
ztane Feb 15, 2026
1a91904
fix(tests): ensure test user exists before auth event tests
ztane Feb 15, 2026
a5862c0
test: add coverage tests, fix cookie path bug
ztane Feb 15, 2026
3f58fd8
fix(tests): use main test user for password endpoint tests
ztane Feb 15, 2026
df3668e
Add comprehensive MFA/TOTP test coverage
ztane Feb 15, 2026
22e44c5
Handle missing table in session-scoped MFA cleanup
ztane Feb 15, 2026
b61c7ed
Add error path and view coverage tests
ztane Feb 15, 2026
d2f428f
Refactor views auth checks, fix audit logging, boost coverage to 99%
ztane Feb 15, 2026
d8611f7
Remove dead mfa_method_type check in disable_mfa_method
ztane Feb 15, 2026
291854b
Fix authorization policy wrapping to use providedBy instead of isinst…
ztane Feb 15, 2026
4a14ab8
Clean up views: fix auth ordering, add error handling, fix test secret
ztane Feb 15, 2026
cfeff08
Fix deprecated pyramid.security imports and test key length warning
ztane Feb 15, 2026
4913fda
Fix passlib deprecation: use hash() instead of encrypt()
ztane Feb 15, 2026
b7b3da4
Move json_body parsing inside try blocks for disable_mfa and revoke_t…
ztane Feb 15, 2026
23fb963
Fix SQLAlchemy deprecation warnings for declarative imports
ztane Feb 15, 2026
d4eb247
Fix security issues in auth module
ztane Jun 13, 2026
91c3f59
Add TOTP replay protection, rate limiting, token cleanup, and Pyramid…
ztane Jun 14, 2026
addf4bf
Add tet.services to public API contract tests
ztane Jun 14, 2026
7f33fa0
Add Sphinx autodoc for all new security modules
ztane Jun 14, 2026
be68dbe
Add security guide and fix pyramid_di Sphinx warnings
ztane Jun 15, 2026
3f18f43
Prepare 0.6a1 release
ztane Jun 15, 2026
112e960
Update pyproject.toml for 0.6a1, restore Sphinx hook, fix auth tests
ztane Jun 15, 2026
fc10fc4
Add security extra and missing test deps so CI installs them
ztane Jun 15, 2026
0340a93
Add pyramid_tm/zope.sqlalchemy to security extra; make test DB URL co…
ztane Jun 15, 2026
8597888
CI: add Python 3.15 nightly (non-blocking)
ztane Jun 15, 2026
4692569
Merge remote-tracking branch 'upstream/master' into jwt-token-auth
ztane Jun 17, 2026
19d9712
ruff: drop .bzr/.hg/.svn from exclude list (unused VCS)
ztane Jun 18, 2026
e5d07bf
Drop black entirely; use ruff/ruff-format
ztane Jun 18, 2026
460587f
security: log the actual error when the breach check fails
ztane Jun 19, 2026
50a06f8
Merge branch 'master' into jwt-token-auth
ztane Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,49 @@ on:
jobs:
test:
runs-on: ubuntu-latest
# 3.15 is an unreleased nightly; allow it to fail without breaking the build.
continue-on-error: ${{ endsWith(matrix.python-version, '-dev') }}
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test_tet
POSTGRES_PASSWORD: test_tet
POSTGRES_DB: test_tet
ports:
- 5432:5432
options: >-
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
strategy:
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
- "3.15-dev"
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install dependencies
run: pip install -e '.[dev]'
- name: Run tests
run: pytest
run: |
pip install 'setuptools<82'
pip install -e '.[dev]'
- name: Run tests with coverage
run: |
pytest --cov=tet --cov-report=term-missing --cov-report=xml:coverage.xml -v
- name: Coverage summary
if: always()
run: |
echo "## Test Coverage" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
python -m coverage report --skip-covered --skip-empty >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

# Publishing to PyPI is handled by release.yml (OIDC trusted publishing).
5 changes: 2 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
repos:
# Python formatting and linting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
rev: v0.9.3 # Use the latest stable version
hooks:
- id: ruff
args: [--fix]
args: [--fix] # Automatically fix lint errors
- id: ruff-format

# reStructuredText checking
Expand Down
19 changes: 19 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
# Changes


2026-06-17 Antti Haapala <antti.haapala@anttipatterns.com>

* 0.6a2: Add ``tet.security`` module with JWT-based authentication,
refresh tokens, TOTP multi-factor authentication, login rate limiting,
password management, and Pyramid security policy integration.
Installable via the ``tet[security]`` extra.
* New SQLAlchemy model mixins: ``TokenMixin``,
``MultiFactorAuthenticationMethodMixin``, ``TOTPUsedCodeMixin``,
``RateLimitAttemptMixin``.
* Pyramid 2.0 compatibility for security/authorization imports.
* Auth views registered via ``config.include("tet.security.authentication")``.
* Event system for login, logout, password change, MFA, and token
revocation (``tet.security.events``).
* TOTP replay protection via UNLOGGED tables.
* Login rate limiting by client IP.
* Breached password checking via Have I Been Pwned API (k-anonymity).
* Drop Python 3.8, 3.9 support. Require Python >= 3.10.


2026-06-16 Antti Haapala <antti.haapala@anttipatterns.com>

* 0.6a1: first 0.6 alpha. Migrated to a ``src/`` layout and replaced
Expand Down
59 changes: 59 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# TODO

## Upstream

- Pyramid depends on `pkg_resources` which was removed in setuptools 82.
Pin `setuptools<82` until Pyramid releases a fix.

## Security

### TOTP replay protection — DONE
- ~~Add an UNLOGGED PostgreSQL table to track used TOTP time steps per user~~
- Implemented via `TOTPUsedCodeMixin` + `FOR UPDATE` on the MFA method row
- `cleanup_used_codes()` method available for periodic cleanup

### Rate limiting — DONE
- ~~Login endpoint has no rate limiting~~
- Implemented via `RateLimitAttemptMixin` (UNLOGGED table) + `TetRateLimitService`
- Login endpoint rate-limited by client IP (configurable max attempts / window)
- Rate limit records use a separate DB connection to survive transaction rollback
- Extend to refresh, MFA, and password change endpoints as needed

### Token cleanup — DONE
- ~~No mechanism to purge expired long-term tokens~~
- `TetTokenService.cleanup_expired_tokens()` available — call from a cron job or admin endpoint

### Pyramid 2.0 compatibility — DONE
- ~~`NO_PERMISSION_REQUIRED` still imported from `pyramid.security`~~
- Now uses try/except compatibility import (pyramid.authorization → pyramid.security fallback)
- CI matrix tests both Pyramid ~=1.9.0 and ~=2.0

### TOTP secret encryption at rest
- TOTP secrets are stored as plaintext in the `data` JSONB column
- Consider encrypting with an application-level key before storing

### Future rate limiting
- Rate limit token refresh, MFA verify, and password change endpoints
- Per-account rate limiting (in addition to per-IP)

## Input validation
- `create_long_term_token()` — validate `user_id` is not None
- `retrieve_and_validate_token()` — validate token format before DB lookup
- `create_short_term_jwt()` — validate user_id is JSON-serializable

## Architecture

### TOTP verification in PL/pgSQL (future)
- Move TOTP verification into a PL/pgSQL function for fully atomic
verify + replay check + insert (requires `pgcrypto` for HMAC-SHA1)
- Current approach (Python verify + FOR UPDATE + insert) is safe but
requires a round trip per step

### Test infrastructure
- Tests depend on a running PostgreSQL instance (`test_tet` database)
- Consider testcontainers or similar for CI portability

## Documentation
- Document why `require_csrf=False` on all auth endpoints (stateless Bearer token auth)
- Document the security model: token lifecycle, MFA flow, cookie handling
- API endpoint documentation (beyond the existing `docs/authentication_apis.md`)
11 changes: 11 additions & 0 deletions docs/api/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,19 @@ API Reference
tet.request
tet.response
tet.security
tet.security.auth
tet.security.authentication
tet.security.authorization
tet.security.compat
tet.security.config
tet.security.csrf
tet.security.events
tet.security.mfa
tet.security.models
tet.security.policy
tet.security.rate_limit
tet.security.tokens
tet.security.views
tet.services
tet.session
tet.sqlalchemy
Expand Down
7 changes: 7 additions & 0 deletions docs/api/tet.security.auth.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.auth module
========================

.. automodule:: tet.security.auth
:members:
:show-inheritance:
:undoc-members:
7 changes: 7 additions & 0 deletions docs/api/tet.security.authentication.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.authentication module
====================================

.. automodule:: tet.security.authentication
:members:
:show-inheritance:
:undoc-members:
1 change: 1 addition & 0 deletions docs/api/tet.security.authorization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ tet.security.authorization module
:members:
:show-inheritance:
:undoc-members:
:no-index:
7 changes: 7 additions & 0 deletions docs/api/tet.security.compat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.compat module
==========================

.. automodule:: tet.security.compat
:members:
:show-inheritance:
:undoc-members:
7 changes: 7 additions & 0 deletions docs/api/tet.security.config.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.config module
==========================

.. automodule:: tet.security.config
:members:
:show-inheritance:
:undoc-members:
7 changes: 7 additions & 0 deletions docs/api/tet.security.events.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.events module
==========================

.. automodule:: tet.security.events
:members:
:show-inheritance:
:undoc-members:
7 changes: 7 additions & 0 deletions docs/api/tet.security.mfa.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.mfa module
=======================

.. automodule:: tet.security.mfa
:members:
:show-inheritance:
:undoc-members:
7 changes: 7 additions & 0 deletions docs/api/tet.security.models.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.models module
==========================

.. automodule:: tet.security.models
:members:
:show-inheritance:
:undoc-members:
7 changes: 7 additions & 0 deletions docs/api/tet.security.policy.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.policy module
==========================

.. automodule:: tet.security.policy
:members:
:show-inheritance:
:undoc-members:
7 changes: 7 additions & 0 deletions docs/api/tet.security.rate_limit.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.rate_limit module
==============================

.. automodule:: tet.security.rate_limit
:members:
:show-inheritance:
:undoc-members:
1 change: 1 addition & 0 deletions docs/api/tet.security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ tet.security
:members:
:show-inheritance:
:undoc-members:
:no-index:
7 changes: 7 additions & 0 deletions docs/api/tet.security.tokens.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.tokens module
==========================

.. automodule:: tet.security.tokens
:members:
:show-inheritance:
:undoc-members:
7 changes: 7 additions & 0 deletions docs/api/tet.security.views.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tet.security.views module
=========================

.. automodule:: tet.security.views
:members:
:show-inheritance:
:undoc-members:
Loading