Skip to main content

hawkapi-users

Full user lifecycle for HawkAPI. Register, login, email verification, password reset — all on top of three existing plugins:

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 type claim 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/request and /password-reset/request always 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 — email is 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 returns 409 Conflict. An attacker can confirm whether any email has an account. Mitigation: rate-limit /register per source IP and require CAPTCHA for unauthenticated traffic. The anti-enumeration guarantee applies only to /verify/request and /password-reset/request (always 202).
  • Inactive-account 403 confirms credentials — /login returns 401 for bad credentials and 403 for a disabled account. A 403 confirms the email+password combination is correct. Treat 403 as a credential-leak event in audit pipelines.
  • No rate limiting — /login, /register, /verify/request, /password-reset/request have no built-in throttling. Pair with hawkapi-ratelimit plus 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)

Source distribution for hawkapi-users 0.2.0
File Size Uploaded
hawkapi_users-0.2.0.tar.gz 49.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hawkapi-users 0.2.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page