Skip to content

Hawk-API/hawkapi-users

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 lookupauthenticate() runs argon2id against a dummy hash when the user does not exist, equalizing wall-clock time across the hit/miss paths.
  • Email normalizationemail 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.

About

User lifecycle for HawkAPI — register, login, password reset, email verification, OAuth providers

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors