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.
- regstack/__init__.py +5 -0
- regstack/app.py +150 -0
- regstack/auth/__init__.py +21 -0
- regstack/auth/clock.py +29 -0
- regstack/auth/dependencies.py +102 -0
- regstack/auth/jwt.py +145 -0
- regstack/auth/lockout.py +59 -0
- regstack/auth/mfa.py +29 -0
- regstack/auth/password.py +20 -0
- regstack/auth/tokens.py +19 -0
- regstack/cli/__init__.py +0 -0
- regstack/cli/__main__.py +27 -0
- regstack/cli/_runtime.py +39 -0
- regstack/cli/admin.py +45 -0
- regstack/cli/doctor.py +186 -0
- regstack/cli/init.py +236 -0
- regstack/config/__init__.py +4 -0
- regstack/config/loader.py +114 -0
- regstack/config/schema.py +148 -0
- regstack/config/secrets.py +22 -0
- regstack/db/__init__.py +17 -0
- regstack/db/client.py +26 -0
- regstack/db/indexes.py +70 -0
- regstack/db/repositories/__init__.py +0 -0
- regstack/db/repositories/blacklist_repo.py +28 -0
- regstack/db/repositories/login_attempt_repo.py +27 -0
- regstack/db/repositories/mfa_code_repo.py +99 -0
- regstack/db/repositories/pending_repo.py +76 -0
- regstack/db/repositories/user_repo.py +169 -0
- regstack/email/__init__.py +12 -0
- regstack/email/base.py +23 -0
- regstack/email/composer.py +142 -0
- regstack/email/console.py +28 -0
- regstack/email/factory.py +23 -0
- regstack/email/ses.py +47 -0
- regstack/email/smtp.py +46 -0
- regstack/email/templates/email_change.html +15 -0
- regstack/email/templates/email_change.subject.txt +1 -0
- regstack/email/templates/email_change.txt +7 -0
- regstack/email/templates/password_reset.html +15 -0
- regstack/email/templates/password_reset.subject.txt +1 -0
- regstack/email/templates/password_reset.txt +7 -0
- regstack/email/templates/sms_login_mfa.txt +1 -0
- regstack/email/templates/sms_phone_setup.txt +1 -0
- regstack/email/templates/verification.html +15 -0
- regstack/email/templates/verification.subject.txt +1 -0
- regstack/email/templates/verification.txt +7 -0
- regstack/hooks/__init__.py +3 -0
- regstack/hooks/events.py +59 -0
- regstack/models/__init__.py +15 -0
- regstack/models/_objectid.py +30 -0
- regstack/models/login_attempt.py +31 -0
- regstack/models/mfa_code.py +40 -0
- regstack/models/pending_registration.py +38 -0
- regstack/models/user.py +104 -0
- regstack/routers/__init__.py +37 -0
- regstack/routers/_schemas.py +34 -0
- regstack/routers/account.py +274 -0
- regstack/routers/admin.py +187 -0
- regstack/routers/login.py +223 -0
- regstack/routers/logout.py +39 -0
- regstack/routers/password.py +114 -0
- regstack/routers/phone.py +242 -0
- regstack/routers/register.py +99 -0
- regstack/routers/verify.py +116 -0
- regstack/sms/__init__.py +5 -0
- regstack/sms/base.py +24 -0
- regstack/sms/factory.py +23 -0
- regstack/sms/null.py +26 -0
- regstack/sms/sns.py +42 -0
- regstack/sms/twilio.py +49 -0
- regstack/ui/__init__.py +3 -0
- regstack/ui/pages.py +148 -0
- regstack/ui/static/css/core.css +204 -0
- regstack/ui/static/css/theme.css +43 -0
- regstack/ui/static/js/regstack.js +411 -0
- regstack/ui/templates/auth/email_change_confirm.html +10 -0
- regstack/ui/templates/auth/forgot.html +14 -0
- regstack/ui/templates/auth/login.html +24 -0
- regstack/ui/templates/auth/me.html +110 -0
- regstack/ui/templates/auth/mfa_confirm.html +14 -0
- regstack/ui/templates/auth/register.html +23 -0
- regstack/ui/templates/auth/reset.html +13 -0
- regstack/ui/templates/auth/verify.html +10 -0
- regstack/ui/templates/base.html +46 -0
- regstack/version.py +1 -0
- regstack-0.1.0.dist-info/METADATA +209 -0
- regstack-0.1.0.dist-info/RECORD +92 -0
- regstack-0.1.0.dist-info/WHEEL +4 -0
- regstack-0.1.0.dist-info/entry_points.txt +2 -0
- regstack-0.1.0.dist-info/licenses/LICENSE +202 -0
- 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
|
+
[](https://github.com/jdrumgoole/regstack/actions/workflows/test.yml)
|
|
60
|
+
[](https://www.python.org/)
|
|
61
|
+
[](https://fastapi.tiangolo.com/)
|
|
62
|
+
[](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
|
+
·
|
|
70
|
+
🧪 **Try it:** [`examples/minimal`](examples/minimal/)
|
|
71
|
+
·
|
|
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,,
|