regstack 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. regstack/__init__.py +5 -0
  2. regstack/app.py +150 -0
  3. regstack/auth/__init__.py +21 -0
  4. regstack/auth/clock.py +29 -0
  5. regstack/auth/dependencies.py +102 -0
  6. regstack/auth/jwt.py +145 -0
  7. regstack/auth/lockout.py +59 -0
  8. regstack/auth/mfa.py +29 -0
  9. regstack/auth/password.py +20 -0
  10. regstack/auth/tokens.py +19 -0
  11. regstack/cli/__init__.py +0 -0
  12. regstack/cli/__main__.py +27 -0
  13. regstack/cli/_runtime.py +39 -0
  14. regstack/cli/admin.py +45 -0
  15. regstack/cli/doctor.py +186 -0
  16. regstack/cli/init.py +236 -0
  17. regstack/config/__init__.py +4 -0
  18. regstack/config/loader.py +114 -0
  19. regstack/config/schema.py +148 -0
  20. regstack/config/secrets.py +22 -0
  21. regstack/db/__init__.py +17 -0
  22. regstack/db/client.py +26 -0
  23. regstack/db/indexes.py +70 -0
  24. regstack/db/repositories/__init__.py +0 -0
  25. regstack/db/repositories/blacklist_repo.py +28 -0
  26. regstack/db/repositories/login_attempt_repo.py +27 -0
  27. regstack/db/repositories/mfa_code_repo.py +99 -0
  28. regstack/db/repositories/pending_repo.py +76 -0
  29. regstack/db/repositories/user_repo.py +169 -0
  30. regstack/email/__init__.py +12 -0
  31. regstack/email/base.py +23 -0
  32. regstack/email/composer.py +142 -0
  33. regstack/email/console.py +28 -0
  34. regstack/email/factory.py +23 -0
  35. regstack/email/ses.py +47 -0
  36. regstack/email/smtp.py +46 -0
  37. regstack/email/templates/email_change.html +15 -0
  38. regstack/email/templates/email_change.subject.txt +1 -0
  39. regstack/email/templates/email_change.txt +7 -0
  40. regstack/email/templates/password_reset.html +15 -0
  41. regstack/email/templates/password_reset.subject.txt +1 -0
  42. regstack/email/templates/password_reset.txt +7 -0
  43. regstack/email/templates/sms_login_mfa.txt +1 -0
  44. regstack/email/templates/sms_phone_setup.txt +1 -0
  45. regstack/email/templates/verification.html +15 -0
  46. regstack/email/templates/verification.subject.txt +1 -0
  47. regstack/email/templates/verification.txt +7 -0
  48. regstack/hooks/__init__.py +3 -0
  49. regstack/hooks/events.py +59 -0
  50. regstack/models/__init__.py +15 -0
  51. regstack/models/_objectid.py +30 -0
  52. regstack/models/login_attempt.py +31 -0
  53. regstack/models/mfa_code.py +40 -0
  54. regstack/models/pending_registration.py +38 -0
  55. regstack/models/user.py +104 -0
  56. regstack/routers/__init__.py +37 -0
  57. regstack/routers/_schemas.py +34 -0
  58. regstack/routers/account.py +274 -0
  59. regstack/routers/admin.py +187 -0
  60. regstack/routers/login.py +223 -0
  61. regstack/routers/logout.py +39 -0
  62. regstack/routers/password.py +114 -0
  63. regstack/routers/phone.py +242 -0
  64. regstack/routers/register.py +99 -0
  65. regstack/routers/verify.py +116 -0
  66. regstack/sms/__init__.py +5 -0
  67. regstack/sms/base.py +24 -0
  68. regstack/sms/factory.py +23 -0
  69. regstack/sms/null.py +26 -0
  70. regstack/sms/sns.py +42 -0
  71. regstack/sms/twilio.py +49 -0
  72. regstack/ui/__init__.py +3 -0
  73. regstack/ui/pages.py +148 -0
  74. regstack/ui/static/css/core.css +204 -0
  75. regstack/ui/static/css/theme.css +43 -0
  76. regstack/ui/static/js/regstack.js +411 -0
  77. regstack/ui/templates/auth/email_change_confirm.html +10 -0
  78. regstack/ui/templates/auth/forgot.html +14 -0
  79. regstack/ui/templates/auth/login.html +24 -0
  80. regstack/ui/templates/auth/me.html +110 -0
  81. regstack/ui/templates/auth/mfa_confirm.html +14 -0
  82. regstack/ui/templates/auth/register.html +23 -0
  83. regstack/ui/templates/auth/reset.html +13 -0
  84. regstack/ui/templates/auth/verify.html +10 -0
  85. regstack/ui/templates/base.html +46 -0
  86. regstack/version.py +1 -0
  87. regstack-0.1.0.dist-info/METADATA +209 -0
  88. regstack-0.1.0.dist-info/RECORD +92 -0
  89. regstack-0.1.0.dist-info/WHEEL +4 -0
  90. regstack-0.1.0.dist-info/entry_points.txt +2 -0
  91. regstack-0.1.0.dist-info/licenses/LICENSE +202 -0
  92. regstack-0.1.0.dist-info/licenses/NOTICE +5 -0
@@ -0,0 +1,110 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Your account — {{ app_name }}{% endblock %}
3
+ {% block content %}
4
+ <h1 class="rs-title">Your account</h1>
5
+
6
+ <dl class="rs-account-summary" data-rs-account>
7
+ <dt>Email</dt><dd data-rs-field="email">…</dd>
8
+ <dt>Full name</dt><dd data-rs-field="full_name">…</dd>
9
+ <dt>Verified</dt><dd data-rs-field="is_verified">…</dd>
10
+ <dt>Member since</dt><dd data-rs-field="created_at">…</dd>
11
+ </dl>
12
+
13
+ <details class="rs-section">
14
+ <summary>Update profile</summary>
15
+ <form class="rs-form" data-rs-form="update-profile">
16
+ <label class="rs-field">
17
+ <span class="rs-label">Full name</span>
18
+ <input type="text" name="full_name" autocomplete="name">
19
+ </label>
20
+ <button type="submit" class="rs-btn">Save</button>
21
+ </form>
22
+ </details>
23
+
24
+ <details class="rs-section">
25
+ <summary>Change password</summary>
26
+ <form class="rs-form" data-rs-form="change-password" autocomplete="off">
27
+ <label class="rs-field">
28
+ <span class="rs-label">Current password</span>
29
+ <input type="password" name="current_password" required minlength="8" autocomplete="current-password">
30
+ </label>
31
+ <label class="rs-field">
32
+ <span class="rs-label">New password</span>
33
+ <input type="password" name="new_password" required minlength="8" autocomplete="new-password">
34
+ </label>
35
+ <button type="submit" class="rs-btn">Change password</button>
36
+ </form>
37
+ </details>
38
+
39
+ <details class="rs-section">
40
+ <summary>Change email</summary>
41
+ <form class="rs-form" data-rs-form="change-email" autocomplete="off">
42
+ <label class="rs-field">
43
+ <span class="rs-label">New email</span>
44
+ <input type="email" name="new_email" required autocomplete="email">
45
+ </label>
46
+ <label class="rs-field">
47
+ <span class="rs-label">Current password</span>
48
+ <input type="password" name="current_password" required minlength="8" autocomplete="current-password">
49
+ </label>
50
+ <button type="submit" class="rs-btn">Send confirmation email</button>
51
+ </form>
52
+ </details>
53
+
54
+ {% if enable_sms_2fa %}
55
+ <details class="rs-section" data-rs-mfa-section>
56
+ <summary>SMS two-factor authentication</summary>
57
+ <p class="rs-meta" data-rs-mfa-status>…</p>
58
+
59
+ <div data-rs-mfa-enable hidden>
60
+ <form class="rs-form" data-rs-form="phone-setup-start" autocomplete="off">
61
+ <label class="rs-field">
62
+ <span class="rs-label">Phone (E.164, e.g. +14155552671)</span>
63
+ <input type="tel" name="phone_number" required pattern="\+[1-9]\d{1,14}">
64
+ </label>
65
+ <label class="rs-field">
66
+ <span class="rs-label">Current password</span>
67
+ <input type="password" name="current_password" required minlength="8" autocomplete="current-password">
68
+ </label>
69
+ <button type="submit" class="rs-btn">Send verification code</button>
70
+ </form>
71
+
72
+ <form class="rs-form" data-rs-form="phone-setup-confirm" autocomplete="off" hidden>
73
+ <label class="rs-field">
74
+ <span class="rs-label">Code from SMS</span>
75
+ <input type="text" name="code" required minlength="4" maxlength="10" inputmode="numeric" pattern="\d+">
76
+ </label>
77
+ <button type="submit" class="rs-btn rs-btn-primary">Confirm</button>
78
+ </form>
79
+ </div>
80
+
81
+ <div data-rs-mfa-disable hidden>
82
+ <form class="rs-form" data-rs-form="mfa-disable" autocomplete="off">
83
+ <label class="rs-field">
84
+ <span class="rs-label">Confirm with password</span>
85
+ <input type="password" name="current_password" required minlength="8" autocomplete="current-password">
86
+ </label>
87
+ <button type="submit" class="rs-btn">Disable SMS 2FA</button>
88
+ </form>
89
+ </div>
90
+ </details>
91
+ {% endif %}
92
+
93
+ {% if enable_account_deletion %}
94
+ <details class="rs-section rs-section-danger">
95
+ <summary>Delete account</summary>
96
+ <form class="rs-form" data-rs-form="delete-account" autocomplete="off">
97
+ <p class="rs-meta">This permanently removes your account and cannot be undone.</p>
98
+ <label class="rs-field">
99
+ <span class="rs-label">Confirm with password</span>
100
+ <input type="password" name="current_password" required minlength="8" autocomplete="current-password">
101
+ </label>
102
+ <button type="submit" class="rs-btn rs-btn-danger">Delete my account</button>
103
+ </form>
104
+ </details>
105
+ {% endif %}
106
+
107
+ <p class="rs-meta">
108
+ <button class="rs-btn rs-btn-ghost" type="button" data-rs-action="logout">Sign out</button>
109
+ </p>
110
+ {% endblock %}
@@ -0,0 +1,14 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Two-factor sign-in — {{ app_name }}{% endblock %}
3
+ {% block content %}
4
+ <h1 class="rs-title">Enter your sign-in code</h1>
5
+ <p class="rs-meta">We just sent a 6-digit code to your phone. It expires shortly.</p>
6
+ <form class="rs-form" data-rs-form="mfa-confirm" autocomplete="off">
7
+ <label class="rs-field">
8
+ <span class="rs-label">Code</span>
9
+ <input type="text" name="code" required minlength="4" maxlength="10" inputmode="numeric" pattern="\d+" autofocus>
10
+ </label>
11
+ <button type="submit" class="rs-btn rs-btn-primary">Sign in</button>
12
+ </form>
13
+ <p class="rs-meta"><a href="{{ ui_prefix }}/login">Back to sign in</a></p>
14
+ {% endblock %}
@@ -0,0 +1,23 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Create your {{ app_name }} account{% endblock %}
3
+ {% block content %}
4
+ <h1 class="rs-title">Create your account</h1>
5
+ <form class="rs-form" data-rs-form="register" autocomplete="on">
6
+ <label class="rs-field">
7
+ <span class="rs-label">Email</span>
8
+ <input type="email" name="email" required autocomplete="email" autofocus>
9
+ </label>
10
+ <label class="rs-field">
11
+ <span class="rs-label">Full name <small>(optional)</small></span>
12
+ <input type="text" name="full_name" autocomplete="name">
13
+ </label>
14
+ <label class="rs-field">
15
+ <span class="rs-label">Password</span>
16
+ <input type="password" name="password" required minlength="8" autocomplete="new-password">
17
+ </label>
18
+ <button type="submit" class="rs-btn rs-btn-primary">Create account</button>
19
+ </form>
20
+ <p class="rs-meta">
21
+ Already have one? <a href="{{ ui_prefix }}/login">Sign in</a>.
22
+ </p>
23
+ {% endblock %}
@@ -0,0 +1,13 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Choose a new password — {{ app_name }}{% endblock %}
3
+ {% block content %}
4
+ <h1 class="rs-title">Choose a new password</h1>
5
+ <form class="rs-form" data-rs-form="reset" autocomplete="off">
6
+ <input type="hidden" name="token" data-rs-token>
7
+ <label class="rs-field">
8
+ <span class="rs-label">New password</span>
9
+ <input type="password" name="new_password" required minlength="8" autocomplete="new-password" autofocus>
10
+ </label>
11
+ <button type="submit" class="rs-btn rs-btn-primary">Save new password</button>
12
+ </form>
13
+ {% endblock %}
@@ -0,0 +1,10 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Confirm your email — {{ app_name }}{% endblock %}
3
+ {% block content %}
4
+ <h1 class="rs-title">Confirming your email…</h1>
5
+ <p class="rs-meta" data-rs-status="pending">Hold on while we verify your account.</p>
6
+ <input type="hidden" data-rs-token>
7
+ <p class="rs-meta">
8
+ <a href="{{ ui_prefix }}/login">Sign in</a>
9
+ </p>
10
+ {% endblock %}
@@ -0,0 +1,46 @@
1
+ <!doctype html>
2
+ <html lang="en" data-rs-page="{{ page }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{% block title %}{{ app_name }}{% endblock %}</title>
7
+ <link rel="stylesheet" href="{{ static_prefix }}/css/core.css">
8
+ <link rel="stylesheet" href="{{ static_prefix }}/css/theme.css">
9
+ {% if theme_css_url %}
10
+ <link rel="stylesheet" href="{{ theme_css_url }}">
11
+ {% endif %}
12
+ {% block extra_head %}{% endblock %}
13
+ </head>
14
+ <body
15
+ data-rs-api="{{ api_prefix }}"
16
+ data-rs-ui="{{ ui_prefix }}"
17
+ data-rs-page="{{ page }}"
18
+ >
19
+ <header class="rs-header">
20
+ {% block brand %}
21
+ <a class="rs-brand" href="{{ ui_prefix }}/me">
22
+ {% if brand_logo_url %}<img class="rs-brand-logo" src="{{ brand_logo_url }}" alt="">{% endif %}
23
+ <span class="rs-brand-name">{{ app_name }}</span>
24
+ </a>
25
+ {% if brand_tagline %}
26
+ <span class="rs-brand-tagline">{{ brand_tagline }}</span>
27
+ {% endif %}
28
+ {% endblock %}
29
+ </header>
30
+
31
+ <main class="rs-main">
32
+ <div class="rs-card">
33
+ {% block content %}{% endblock %}
34
+ <p class="rs-message" data-rs-message hidden></p>
35
+ </div>
36
+ </main>
37
+
38
+ <footer class="rs-footer">
39
+ {% block footer %}
40
+ <span>Powered by <a href="https://github.com/jdrumgoole/regstack">regstack</a></span>
41
+ {% endblock %}
42
+ </footer>
43
+
44
+ <script src="{{ static_prefix }}/js/regstack.js" defer></script>
45
+ </body>
46
+ </html>
regstack/version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: regstack
3
+ Version: 0.1.0
4
+ Summary: Embeddable user registration, login, and account management for FastAPI/MongoDB apps.
5
+ Project-URL: Homepage, https://github.com/jdrumgoole/regstack
6
+ Project-URL: Repository, https://github.com/jdrumgoole/regstack
7
+ Author-email: Joe Drumgoole <joe@joedrumgoole.com>
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ License-File: NOTICE
11
+ Keywords: auth,fastapi,mongodb,registration,users
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: aiosmtplib>=3.0
20
+ Requires-Dist: click>=8.1
21
+ Requires-Dist: dnspython>=2.6
22
+ Requires-Dist: email-validator>=2.1
23
+ Requires-Dist: fastapi>=0.110
24
+ Requires-Dist: jinja2>=3.1
25
+ Requires-Dist: pwdlib[argon2]>=0.2.1
26
+ Requires-Dist: pydantic-settings>=2.2
27
+ Requires-Dist: pydantic>=2.6
28
+ Requires-Dist: pyjwt>=2.8
29
+ Requires-Dist: pymongo>=4.9
30
+ Requires-Dist: python-multipart>=0.0.9
31
+ Provides-Extra: dev
32
+ Requires-Dist: anyio>=4.3; extra == 'dev'
33
+ Requires-Dist: httpx>=0.27; extra == 'dev'
34
+ Requires-Dist: invoke>=2.2; extra == 'dev'
35
+ Requires-Dist: mypy>=1.10; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
38
+ Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
39
+ Requires-Dist: pytest>=8.0; extra == 'dev'
40
+ Requires-Dist: ruff>=0.4; extra == 'dev'
41
+ Requires-Dist: uvicorn[standard]>=0.29; extra == 'dev'
42
+ Provides-Extra: docs
43
+ Requires-Dist: furo>=2024.1; extra == 'docs'
44
+ Requires-Dist: myst-parser>=3.0; extra == 'docs'
45
+ Requires-Dist: sphinx-autobuild>=2024.4; extra == 'docs'
46
+ Requires-Dist: sphinx-autodoc-typehints>=2.0; extra == 'docs'
47
+ Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
48
+ Requires-Dist: sphinx>=7.3; extra == 'docs'
49
+ Provides-Extra: ses
50
+ Requires-Dist: aioboto3>=12.3; extra == 'ses'
51
+ Provides-Extra: sns
52
+ Requires-Dist: aioboto3>=12.3; extra == 'sns'
53
+ Provides-Extra: twilio
54
+ Requires-Dist: twilio>=9.0; extra == 'twilio'
55
+ Description-Content-Type: text/markdown
56
+
57
+ # regstack
58
+
59
+ [![CI](https://github.com/jdrumgoole/regstack/actions/workflows/test.yml/badge.svg)](https://github.com/jdrumgoole/regstack/actions/workflows/test.yml)
60
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
61
+ [![FastAPI](https://img.shields.io/badge/built%20with-FastAPI-009688.svg)](https://fastapi.tiangolo.com/)
62
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE)
63
+
64
+ **Drop-in user accounts for FastAPI + MongoDB.** Stop hand-rolling
65
+ register / login / verify / reset / 2FA in every project — install
66
+ regstack, point it at your MongoDB, and you're done.
67
+
68
+ 📚 **Docs:** <https://regstack.readthedocs.io>
69
+ &nbsp;·&nbsp;
70
+ 🧪 **Try it:** [`examples/minimal`](examples/minimal/)
71
+ &nbsp;·&nbsp;
72
+ 🛡️ **Security model:** [`docs/security.md`](docs/security.md)
73
+
74
+ ---
75
+
76
+ ## What you get
77
+
78
+ ```
79
+ ✔ Email + password registration with verification
80
+ ✔ JWT login with per-token revocation AND bulk revocation
81
+ ✔ Forgot / reset password (anti-enumeration)
82
+ ✔ Change password / change email / delete account
83
+ ✔ Optional SMS two-factor authentication
84
+ ✔ Server-side login lockout (HTTP 429 + Retry-After)
85
+ ✔ Admin endpoints (list / disable / delete users, stats)
86
+ ✔ Server-rendered HTML UI you can theme with one CSS file
87
+ ✔ Pluggable email (console / SMTP / SES) and SMS (null / SNS / Twilio)
88
+ ✔ Argon2 password hashing, CSP-friendly templates, anti-enumeration
89
+ ✔ Setup wizard (`regstack init`) and health-check (`regstack doctor`)
90
+ ```
91
+
92
+ Every feature is opt-in. Mount only the JSON router for a headless
93
+ backend; flip `enable_ui_router` to also get the bundled SSR pages.
94
+
95
+ ## Why regstack?
96
+
97
+ Most FastAPI auth tutorials stop at "here's a `/login` route that
98
+ returns a JWT" and leave you to assemble the other 30 things real
99
+ applications need: email verification, password resets, account
100
+ recovery, admin tooling, brute-force protection, MFA, themed pages,
101
+ secure storage of one-time tokens, anti-enumeration, bulk session
102
+ revocation when a password changes…
103
+
104
+ regstack ships **all** of that as one Apache-licensed package, with a
105
+ test suite that runs in parallel against a real MongoDB and a live demo
106
+ you can `curl` end-to-end in two minutes.
107
+
108
+ ## Try it in 30 seconds
109
+
110
+ ```bash
111
+ git clone https://github.com/jdrumgoole/regstack && cd regstack
112
+ uv sync --extra dev
113
+
114
+ # minimal config
115
+ export REGSTACK_JWT_SECRET=$(python -c 'import secrets; print(secrets.token_urlsafe(64))')
116
+ export REGSTACK_MONGODB_URL=mongodb://localhost:27017
117
+
118
+ uv run uvicorn examples.minimal.main:app --reload
119
+ ```
120
+
121
+ Then visit <http://localhost:8000/account/login> in your browser, or:
122
+
123
+ ```bash
124
+ curl -X POST http://localhost:8000/api/auth/register \
125
+ -H 'content-type: application/json' \
126
+ -d '{"email":"alice@example.com","password":"hunter2hunter2","full_name":"Alice"}'
127
+ ```
128
+
129
+ The bundled example serves a themed SSR dashboard at `/account/me`,
130
+ prints verification / reset links and SMS codes to stdout, and
131
+ demonstrates how a host overrides regstack's default theme by serving
132
+ its own `theme.css` from `examples/minimal/branding/`.
133
+
134
+ ## Embed in your own app
135
+
136
+ ```python
137
+ from contextlib import asynccontextmanager
138
+
139
+ from fastapi import FastAPI
140
+
141
+ from regstack import RegStack, RegStackConfig
142
+ from regstack.db.client import make_client
143
+
144
+
145
+ config = RegStackConfig.load()
146
+ mongo = make_client(config)
147
+ db = mongo[config.mongodb_database]
148
+ regstack = RegStack(config=config, db=db)
149
+
150
+
151
+ @asynccontextmanager
152
+ async def lifespan(app: FastAPI):
153
+ await regstack.install_indexes()
154
+ yield
155
+ await mongo.aclose()
156
+
157
+
158
+ app = FastAPI(lifespan=lifespan)
159
+ app.include_router(regstack.router, prefix=config.api_prefix)
160
+ app.include_router(regstack.ui_router, prefix=config.ui_prefix) # optional
161
+ app.mount(config.static_prefix, regstack.static_files) # optional
162
+ ```
163
+
164
+ That's the whole integration. Configure the rest with `regstack.toml`
165
+ or environment variables — see the [configuration
166
+ reference](https://regstack.readthedocs.io/en/latest/configuration.html).
167
+
168
+ ## Documentation
169
+
170
+ | Page | What's there |
171
+ |---|---|
172
+ | [Quickstart](https://regstack.readthedocs.io/en/latest/quickstart.html) | Install, wizard, minimal embed |
173
+ | [Configuration](https://regstack.readthedocs.io/en/latest/configuration.html) | Every `RegStackConfig` field, env vars, TOML layout |
174
+ | [Architecture](https://regstack.readthedocs.io/en/latest/architecture.html) | Façade, repos, hooks, lifecycle |
175
+ | [Security model](https://regstack.readthedocs.io/en/latest/security.html) | Threat model, JWT scheme, anti-enumeration, MFA |
176
+ | [Embedding](https://regstack.readthedocs.io/en/latest/embedding.html) | Custom backends, hooks, multi-tenant |
177
+ | [Theming](https://regstack.readthedocs.io/en/latest/theming.html) | CSS variables, template overrides |
178
+ | [CLI](https://regstack.readthedocs.io/en/latest/cli.html) | `init`, `create-admin`, `doctor` |
179
+ | [API reference](https://regstack.readthedocs.io/en/latest/api.html) | Public types, generated from source |
180
+
181
+ The same docs are also browsable as Markdown in [`docs/`](docs/).
182
+
183
+ ## Status
184
+
185
+ Alpha. Milestones M1 through M6 are complete and verified end-to-end
186
+ in the bundled example. See the [changelog](docs/changelog.md) for the
187
+ per-milestone breakdown. The next tagged release will be `v0.1.0`.
188
+
189
+ ## Contributing
190
+
191
+ Issues and pull requests welcome at
192
+ <https://github.com/jdrumgoole/regstack>. Before opening a PR, please
193
+ run the test suite and the linter — both should be green:
194
+
195
+ ```bash
196
+ uv sync --extra dev
197
+ uv run python -m invoke test # parallel pytest, needs local MongoDB
198
+ uv run python -m invoke lint # ruff + format check + mypy
199
+ ```
200
+
201
+ A local MongoDB on `mongodb://localhost:27017` is required for the
202
+ integration tests. Each pytest-xdist worker creates and drops its own
203
+ database, so the suite is safe to re-run while you iterate.
204
+
205
+ Security disclosures: see [SECURITY.md](SECURITY.md).
206
+
207
+ ## License
208
+
209
+ [Apache License 2.0](LICENSE) © 2026 Joe Drumgoole.
@@ -0,0 +1,92 @@
1
+ regstack/__init__.py,sha256=3OkvDcg08RzXKz2nMCt2kzvl7IXLq5bQBKnI4DgdEhs,234
2
+ regstack/app.py,sha256=JIU4h1uU6brgc5gNsP1n6_Nnackc0ixxeyDeCoDT1vA,6098
3
+ regstack/version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
4
+ regstack/auth/__init__.py,sha256=mQxt-26lb71RIf9eodMf-BKI1XcUg-6jqSsQSylOwE4,652
5
+ regstack/auth/clock.py,sha256=XwKTgiJCVhFylp76yBaqeFREtWID0G5FR8ZlX3ZR54c,684
6
+ regstack/auth/dependencies.py,sha256=cig_QdsestixZgMLTSzWozoHKsbEdF0_5MyHSeg7mmA,3476
7
+ regstack/auth/jwt.py,sha256=ZEsPv5eOKIf6xLOvuO7AUyi_JZoFdeXAIdM_mwRgdQs,4751
8
+ regstack/auth/lockout.py,sha256=uGB67JIvcumXz-NjBjt-uSqxuMXXRAJMOeaWYV38BUE,1995
9
+ regstack/auth/mfa.py,sha256=ZQ8vQ6nSCK7LoEFC0UyO1-h63bA82u8GEpIF5Wk68Fk,975
10
+ regstack/auth/password.py,sha256=ssKt7RMh7W716sabVQUq4g5quu9g1DHsz0mCLh5Akj4,621
11
+ regstack/auth/tokens.py,sha256=liRhvIjyxL6oc2QBXwafTcFoPXy6EZPqkWJUXkc5Pns,541
12
+ regstack/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ regstack/cli/__main__.py,sha256=EDajv5-p9o3ojFHI5gf0ZtaV68R3qNP7lqSM7kkvziw,629
14
+ regstack/cli/_runtime.py,sha256=iYMvYWANMPfk9uDNY2QBJ0X21l_1x37VR8-2vO2grtU,1215
15
+ regstack/cli/admin.py,sha256=6urWbjdx_avKhgff9nqNdK18hwn4W1Cy8iEGyJpI_pM,1472
16
+ regstack/cli/doctor.py,sha256=FFfWJ-1RbnmDvqIPGZeOKBfXLAkYUcHq14M5v2dEW5E,6477
17
+ regstack/cli/init.py,sha256=ZPHbCDREeHTGVq6Fw7kk3bqnzGODrYzwIENDuPAg4XU,8515
18
+ regstack/config/__init__.py,sha256=UNvJSThXv_whB0fph_JfWwCtaA60sH0-p3stXUp3_o0,194
19
+ regstack/config/loader.py,sha256=bMO4DcqJNvaozmLR0i-hQW-DwFz8I98s4lxJ5fonjmA,3729
20
+ regstack/config/schema.py,sha256=UnzBJ6_mjyx3mtzEXmGK6wT9dqk58M_DpX5BIKeHDJo,5287
21
+ regstack/config/secrets.py,sha256=0RPWnVXhNhWOA8AiFhn5ODawTHiYxlfgdmLb6Cwpor8,744
22
+ regstack/db/__init__.py,sha256=ZTU8cUPSwqo41PN_kBEgOh1ZpFyHygZZSvgwnCAwHxw,587
23
+ regstack/db/client.py,sha256=6GzD0IN8ZVzyUCZSkB_osLfDWwb0kRojpf4s8T0TNM0,771
24
+ regstack/db/indexes.py,sha256=xTSvkrzexNvZWvtJ0WpLO4h8WdSuh0QniT-4b_viQA0,2475
25
+ regstack/db/repositories/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ regstack/db/repositories/blacklist_repo.py,sha256=KQ9kCMRITyNbCam8pQGsdW_MmO--_gjn3qR9piyJreQ,958
27
+ regstack/db/repositories/login_attempt_repo.py,sha256=x1Y-Vofx87EbBREtl3whlg0P-C1_DnCO-9ZLi2j-CFY,996
28
+ regstack/db/repositories/mfa_code_repo.py,sha256=55gLzDtmpUU1moqX2eIwkaKLlAKrgTN6cWUouumPFYI,3354
29
+ regstack/db/repositories/pending_repo.py,sha256=g5p3YBRCzuk7FJfIuJNuXR-VvBfBO4Nm7RKL19nwOiA,2887
30
+ regstack/db/repositories/user_repo.py,sha256=1eTaDwko4lpAbzdrUQZANRLGg4PF-KdQWd6Wxu2Ezf0,6066
31
+ regstack/email/__init__.py,sha256=2u-lE8ZHPnIwXfaUHVXfDujfZl1Qwj7yvKCYmD9Nqr4,347
32
+ regstack/email/base.py,sha256=zhMJW5yTyynuGqoZhCmTqhAk0p2jICME-fe1KrqkXBo,476
33
+ regstack/email/composer.py,sha256=sa76mhozPbFmcLVPOJKmM-lCCYpgcqj08WmXmRDJvTI,4700
34
+ regstack/email/console.py,sha256=RgRbQp69YwLnOsE-yGa63GM0OASLTsXDh6jFTMuRv3c,845
35
+ regstack/email/factory.py,sha256=xiqdVX4ieRkJSygjcD-6HbVXSOIDpXUcPdK7BLtldkk,702
36
+ regstack/email/ses.py,sha256=V8d4iB0uUtHT9NihXrGwh4V-qyzOj7rLg5t7pncL0I8,1764
37
+ regstack/email/smtp.py,sha256=cdEVKYy6dtt2oKrlM8drAoNdY4aaLbdAWLVEBXxKIGA,1483
38
+ regstack/email/templates/email_change.html,sha256=7BGIXOnpmKGGvdz1Jzr-3UB78qafN0z99bJAKEmhqQE,958
39
+ regstack/email/templates/email_change.subject.txt,sha256=QOK-C6aRK6YMoRCApy0thADzmYB3STK4TkmHp2fw5jI,46
40
+ regstack/email/templates/email_change.txt,sha256=saoGzHekudHd-mJUnUQMraxqYDXLkyg3WlIlveWyZ58,330
41
+ regstack/email/templates/password_reset.html,sha256=Zj62ajY5zWk806JqHcQPJUc20YEeHVzmCmWXo40C9ZQ,911
42
+ regstack/email/templates/password_reset.subject.txt,sha256=oOVCbeq44Lzi08Azb9qXxu35Xj224q4-5r4n7Sm_OS8,35
43
+ regstack/email/templates/password_reset.txt,sha256=zqVMwMCTK26WzcK8B1JNB_cVlFpIhoOFLBsFdBILrak,285
44
+ regstack/email/templates/sms_login_mfa.txt,sha256=RhpPME-qpt00IZYnj0W16g2JLB7uqf6TLO0SPFCLYow,130
45
+ regstack/email/templates/sms_phone_setup.txt,sha256=5ENwXChsDshU7V5z1g7xBRFoudjSt18uSFY7d8t0wjI,87
46
+ regstack/email/templates/verification.html,sha256=8rrMy3DuSbabHwnqE5wDZzgo3LUguApXHXi-2A5Xvq8,889
47
+ regstack/email/templates/verification.subject.txt,sha256=-lAHGOFQg7zWrVLp6S0dlh2uJ53kYVYrWyfQu2MCMIw,36
48
+ regstack/email/templates/verification.txt,sha256=Z0eiEGgu5FoSxisooA1FWPFV4GZgXcDvbccD8uY8xZY,273
49
+ regstack/hooks/__init__.py,sha256=gRrmCE6Mqu5CXpsT_RF2b1cZpzezh_UmzCeFKGchmH4,75
50
+ regstack/hooks/events.py,sha256=SuVjei7cS027bFQ4uS2z0oJiGV51srC-U3eg-nuA9eQ,1727
51
+ regstack/models/__init__.py,sha256=rINLdHdptcZVJ8_xmEboVa2j-esHxFW-rMlXBJt-Xzo,418
52
+ regstack/models/_objectid.py,sha256=gW2hvXibMrzFJklg1PQGFDSHX9IrOBhLwBu8gv0yEaU,967
53
+ regstack/models/login_attempt.py,sha256=rYxGqOvzK3FX5KTSFQ0ZhYsHIDG2ZaySlr1cF635-CY,854
54
+ regstack/models/mfa_code.py,sha256=zJh7wPvsXewwbz_eHE4pcybgV3Cvu6ChTXHk1Y0hT8Y,1127
55
+ regstack/models/pending_registration.py,sha256=xWSXlgnPeC8AcagVkunj1HxKxZSskajeGsx3w_4pDOQ,1158
56
+ regstack/models/user.py,sha256=cbVVolo2-72B3yG_5E7afmVg-wJcDao80W9rPEcfutE,3021
57
+ regstack/routers/__init__.py,sha256=GsE38EHYRKYek0JQA8_00VQDwiy5ofZlA8iv8uaALko,1380
58
+ regstack/routers/_schemas.py,sha256=IxwV0uFl0x-4MIBvkLiOrYCGVtIt-beP73Su0Bu-Kqg,819
59
+ regstack/routers/account.py,sha256=oxFyn4AxXsdOQutoci2fhc5gYz-gQisnPGMW4Tykrw0,9818
60
+ regstack/routers/admin.py,sha256=k1A_3ZRwDoqw7nPsWskUcjDJNH37ayVIrF_UuQVkubU,6627
61
+ regstack/routers/login.py,sha256=-I7lGbXdry6vMDJsJUEuxa12p4BiYpYJgLNN6CaULSk,8031
62
+ regstack/routers/logout.py,sha256=IVtbekKHcBxTcLw9MZBtpxri-HhluKBmc5NhF4vSdpY,1249
63
+ regstack/routers/password.py,sha256=yv_cS4SoNRxq7MSiUoOmdyscTKjVgX-_aqHqWmn9JWc,4041
64
+ regstack/routers/phone.py,sha256=KGHAOtofT9fDPBZl0c4gxdEGNBmBL0L5XAThPFScg-8,8207
65
+ regstack/routers/register.py,sha256=0PcZM_tpw9tLulwYAcWWrvFGHCopJr2KvvJ2Pynt24Q,3576
66
+ regstack/routers/verify.py,sha256=BTu1hRKba_4j7p52P5_lLpWD_DwSYPIaA5S2JtuVVP8,3935
67
+ regstack/sms/__init__.py,sha256=vWPiZF0VRJNTUd-R_QOcb3v1whfayT1Wpz0s92EfxsI,228
68
+ regstack/sms/base.py,sha256=Fr7nV-RAoNXFhYSY3w37o2youp0b6U-V7ZRa4rJBPeU,537
69
+ regstack/sms/factory.py,sha256=MUHqvZPYkVBUWruGYCwIhiSarkpt-AIJPjfPaGYqvbY,666
70
+ regstack/sms/null.py,sha256=PGi6riA18-6SAPWXw7u_beyMR26fCHoJcxebPi5cYEU,765
71
+ regstack/sms/sns.py,sha256=qYE4-eLTI7ChzRLSliWyGz_SH0jOyTTXBDjgnLUfE74,1410
72
+ regstack/sms/twilio.py,sha256=3OLbnsa6juceA5qIlgPFdfMJ17yhDhUH3TUc7YZErO4,1744
73
+ regstack/ui/__init__.py,sha256=EciQqJx3MxA3OxuDvFT37wIOjXwRJeI7T-ZJQLm0fWA,165
74
+ regstack/ui/pages.py,sha256=yM_4An0Y6s3vU2YcKf9vMaEXlS2spZVsqLNJFRfvrsI,5043
75
+ regstack/ui/static/css/core.css,sha256=eCL0MDESqU3GFS95Qf6okgH6CduSY17T6R98hn07in4,4468
76
+ regstack/ui/static/css/theme.css,sha256=GECznNw5KIrl3mDeqdwwPVhHPlZy-87BbWykW53-w6Q,1244
77
+ regstack/ui/static/js/regstack.js,sha256=zkUfMIeHQqtSKNnTrFUnzQVBP3Chi1fWrqsUDgovozM,13281
78
+ regstack/ui/templates/base.html,sha256=QjxrsSLm9rjtRkaxs4tHrx7NzhrzqRKyaIRki8yHc9E,1365
79
+ regstack/ui/templates/auth/email_change_confirm.html,sha256=SzpdGYyUHf6zt2PhpguCulDuq5nGViEOiUv6RhPsDzY,383
80
+ regstack/ui/templates/auth/forgot.html,sha256=oYsY2JDpctAP5S76JoZeZl6G4tG2Y4TH__11DNjBpD8,623
81
+ regstack/ui/templates/auth/login.html,sha256=91Yv1uPBBHnPeX2TF1BVUn3Yfb4E2zQAGjmcukq9N80,871
82
+ regstack/ui/templates/auth/me.html,sha256=1_Uih-2vpKW5vLppe7KTbfCcqTzJwmyHkem1LL88eQY,4361
83
+ regstack/ui/templates/auth/mfa_confirm.html,sha256=L5E3tHm0pFpwFJJ-yAC0Go7yiZQJE1bO7lgGfQVfjVY,684
84
+ regstack/ui/templates/auth/register.html,sha256=eCiCrasjTCxraepmERgKwN0KN9HI2i7IIs7cl9V3VSQ,915
85
+ regstack/ui/templates/auth/reset.html,sha256=P1kyoGQDl-acKLGKe_R12xvT_5HwH0Gzxq3WZQNVJoM,578
86
+ regstack/ui/templates/auth/verify.html,sha256=VMxLFp9pkgOOZSEX2FD1K-oC6qjyGbnm8eHxLmrlzRo,374
87
+ regstack-0.1.0.dist-info/METADATA,sha256=nPPuneYaP139fJqdIKkBmnqH2-h8A6_kORPAIyndLFc,8146
88
+ regstack-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
89
+ regstack-0.1.0.dist-info/entry_points.txt,sha256=QePQE_N7MisQV8QFjkhPFn374yo-3vFQuK-vQRrd2MQ,56
90
+ regstack-0.1.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
91
+ regstack-0.1.0.dist-info/licenses/NOTICE,sha256=zwOE6BbDNcx2zzQX3sHiv5GLvkVBKLPZNg-Y60b193E,130
92
+ regstack-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ regstack = regstack.cli.__main__:main