User lifecycle for HawkAPI — register, login, password reset, email verification, on top of hawkapi-auth + hawkapi-sqlalchemy + hawkapi-mail
Project description
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/ashimov/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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hawkapi_users-0.1.0.tar.gz.
File metadata
- Download URL: hawkapi_users-0.1.0.tar.gz
- Upload date:
- Size: 48.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9ad482531bcc3bab34ecdbafb000bab9cd4625080307f21cad5c016904a83a2
|
|
| MD5 |
f03052bd9cc7a5bb0f7166dbd7429e75
|
|
| BLAKE2b-256 |
8e43094d671c85c59cb323f5a523d36ade86547dbdb39423561e973ca9747cbb
|
Provenance
The following attestation bundles were made for hawkapi_users-0.1.0.tar.gz:
Publisher:
release.yml on ashimov/hawkapi-users
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hawkapi_users-0.1.0.tar.gz -
Subject digest:
a9ad482531bcc3bab34ecdbafb000bab9cd4625080307f21cad5c016904a83a2 - Sigstore transparency entry: 1554028062
- Sigstore integration time:
-
Permalink:
ashimov/hawkapi-users@bf84416a4d5175fa678475cc7b1cc6e1f01ddf72 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ashimov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bf84416a4d5175fa678475cc7b1cc6e1f01ddf72 -
Trigger Event:
release
-
Statement type:
File details
Details for the file hawkapi_users-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hawkapi_users-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50c8513f54e736311b8a81ed9f8b6876ad179bf6c0886194a5d86e9958e62605
|
|
| MD5 |
e166996c1408d30d21b7aadd14089117
|
|
| BLAKE2b-256 |
6e7590259cf7787bc1ce3d9f9cdf5bdf4a3462ad9f3f16a596b1a82ac1ace485
|
Provenance
The following attestation bundles were made for hawkapi_users-0.1.0-py3-none-any.whl:
Publisher:
release.yml on ashimov/hawkapi-users
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hawkapi_users-0.1.0-py3-none-any.whl -
Subject digest:
50c8513f54e736311b8a81ed9f8b6876ad179bf6c0886194a5d86e9958e62605 - Sigstore transparency entry: 1554028071
- Sigstore integration time:
-
Permalink:
ashimov/hawkapi-users@bf84416a4d5175fa678475cc7b1cc6e1f01ddf72 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ashimov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bf84416a4d5175fa678475cc7b1cc6e1f01ddf72 -
Trigger Event:
release
-
Statement type: