hawkapi-users
Full user lifecycle for HawkAPI. Register, login, email verification, password reset — all on top of three existing plugins:
hawkapi-auth— argon2id passwords, JWT token issuerhawkapi-sqlalchemy— async SQLAlchemy sessionshawkapi-mail— transactional email backends + templates
No fastapi-users dependency; pure stdlib + the three plugins above.
Install
pip install hawkapi-users
Quickstart
from hawkapi import HawkAPI
from hawkapi_sqlalchemy import Base, init_database
from hawkapi_mail import init_mail, SMTPBackend, SMTPConfig
from hawkapi_users import (
SQLAlchemyBaseUserTable,
UserManager, UserTokens, UserTokenConfig,
init_users, send_verification_email, send_password_reset_email,
)
class User(SQLAlchemyBaseUserTable):
__tablename__ = "users"
app = HawkAPI()
init_database(app, url="postgresql+asyncpg://...")
mail = init_mail(
app,
backend=SMTPBackend(SMTPConfig(host="smtp.example.com", port=587, start_tls=True)),
default_sender="noreply@example.com",
)
tokens = UserTokens(UserTokenConfig(secret="…stable secret, ≥32 chars…"))
manager = UserManager(model=User, tokens=tokens)
async def on_request_verify(user, token):
await send_verification_email(
mail, to=user.email, token=token,
verify_url_template="https://app.example.com/verify/{token}",
)
async def on_forgot_password(user, token):
await send_password_reset_email(
mail, to=user.email, token=token,
reset_url_template="https://app.example.com/password-reset/{token}",
)
manager.on_after_request_verify = on_request_verify
manager.on_after_forgot_password = on_forgot_password
init_users(app, manager=manager)
The plugin mounts under /users (configurable):
| Route | Description |
|---|---|
POST /users/register |
Create account, returns public user record |
POST /users/login |
Verify credentials, returns public user |
POST /users/verify/request |
Send a verification email (always 202) |
POST /users/verify/{token} |
Apply a verification token |
POST /users/password-reset/request |
Send a reset email (always 202) |
POST /users/password-reset/{token} |
Apply a new password |
Security defaults
- Argon2id password hashing (via
hawkapi-auth's pinned parameters). - Token type binding — verify tokens cannot be replayed as reset tokens (the
typeclaim is enforced). - password_version binding — each token carries the user's current
password_version. A successful reset bumps the counter, invalidating every outstanding verify/reset token in one DB write. - Account enumeration prevention —
/verify/requestand/password-reset/requestalways return 202 regardless of whether the email exists. - Timing-safe lookup —
authenticate()runs argon2id against a dummy hash when the user does not exist, equalizing wall-clock time across the hit/miss paths. - Email normalization —
emailis lower-cased and stripped server-side; uniqueness is enforced after normalization.
Known accepted risks
These are real tradeoffs the plugin does NOT mitigate. Operators must layer additional protection where it matters.
- Email enumeration via
/register(409 response) — registering with an already-used email returns409 Conflict. An attacker can confirm whether any email has an account. Mitigation: rate-limit/registerper source IP and require CAPTCHA for unauthenticated traffic. The anti-enumeration guarantee applies only to/verify/requestand/password-reset/request(always 202). - Inactive-account 403 confirms credentials —
/loginreturns401for bad credentials and403for a disabled account. A403confirms the email+password combination is correct. Treat403as a credential-leak event in audit pipelines. - No rate limiting —
/login,/register,/verify/request,/password-reset/requesthave no built-in throttling. Pair withhawkapi-ratelimitplus per-account lockout. - No session issued by
/login— the route validates credentials and returns the public user record. Issuing a JWT, setting a cookie, or starting a server-side session is the operator's job (manager.on_after_login). - Token-in-URL leakage — verify/reset tokens travel in URL paths and may appear in Referer headers, browser history, and email-server access logs. Use HTTPS + short TTLs (default 1 hour) and prefer SPA flows that strip the token from the URL after consumption.
Hooks
manager.on_after_register # async (user) -> None
manager.on_after_login # async (user) -> None
manager.on_after_request_verify # async (user, token) -> None
manager.on_after_verify # async (user) -> None
manager.on_after_forgot_password # async (user, token) -> None
manager.on_after_reset_password # async (user) -> None
Use these to mint a session cookie, write an audit log, send the email, etc.
Custom email templates
from hawkapi_mail import TemplateRenderer
from hawkapi_users import send_verification_email
renderer = TemplateRenderer(directory="my/email/templates")
await send_verification_email(
mail,
to=user.email,
token=token,
template="my_verify.html",
text_template="my_verify.txt",
renderer=renderer,
)
The built-in templates live in hawkapi_users/templates/ (verify.html/.txt, password_reset.html/.txt) and are used by default.
Development
git clone https://github.com/Hawk-API/hawkapi-users.git
cd hawkapi-users
uv sync --extra dev
uv run pytest -q
uv run ruff check . && uv run ruff format --check .
uv run pyright src/
License
MIT.
Metadata
Release files for hawkapi-users 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hawkapi_users-0.2.0.tar.gz | 49.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hawkapi_users-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.1 kB
Release files / hawkapi_users-0.2.0.tar.gz
| Download URL | hawkapi_users-0.2.0.tar.gz |
|---|---|
| Size | 49.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e1993e9f4068d27f16628e7e528770f3ef4c091247e3e1597c27355e0e3cefcc
|
|
BLAKE2b-256 checksum How to use checksums |
823569156ad7c5661e1172305c47e04bb578f18df0891ff1ce15b781d1e0beaa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 10, 2026.
Transparency logRelease files / hawkapi_users-0.2.0-py3-none-any.whl
| Download URL | hawkapi_users-0.2.0-py3-none-any.whl |
|---|---|
| Size | 16.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e2fc60914e65b1c014464ae2a68b8dbb82f1343fb306dca03e7f7daced27ef76
|
|
BLAKE2b-256 checksum How to use checksums |
d476a324ca06d649805724105e86c2dc71cd3c603524c4bfd06b6bbb7a38afac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 10, 2026.
Transparency log