microdot 2.4.0__tar.gz → 2.5.0__tar.gz
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.
- {microdot-2.4.0/src/microdot.egg-info → microdot-2.5.0}/PKG-INFO +7 -19
- {microdot-2.4.0 → microdot-2.5.0}/README.md +5 -18
- microdot-2.5.0/docs/api/asgi.rst +6 -0
- microdot-2.5.0/docs/api/auth.rst +7 -0
- microdot-2.5.0/docs/api/cors.rst +5 -0
- microdot-2.5.0/docs/api/csrf.rst +5 -0
- microdot-2.5.0/docs/api/index.rst +21 -0
- microdot-2.5.0/docs/api/jinja.rst +5 -0
- microdot-2.5.0/docs/api/login.rst +7 -0
- microdot-2.5.0/docs/api/microdot.rst +14 -0
- microdot-2.5.0/docs/api/multipart.rst +5 -0
- microdot-2.5.0/docs/api/sessions.rst +5 -0
- microdot-2.5.0/docs/api/sse.rst +5 -0
- microdot-2.5.0/docs/api/test_client.rst +5 -0
- microdot-2.5.0/docs/api/utemplate.rst +5 -0
- microdot-2.5.0/docs/api/websocket.rst +5 -0
- microdot-2.5.0/docs/api/wsgi.rst +6 -0
- {microdot-2.4.0 → microdot-2.5.0}/docs/conf.py +2 -7
- microdot-2.5.0/docs/contributing.rst +7 -0
- microdot-2.5.0/docs/extensions/auth.rst +112 -0
- microdot-2.5.0/docs/extensions/cors.rst +34 -0
- microdot-2.5.0/docs/extensions/csrf.rst +94 -0
- microdot-2.5.0/docs/extensions/index.rst +21 -0
- microdot-2.5.0/docs/extensions/login.rst +85 -0
- microdot-2.5.0/docs/extensions/multipart.rst +73 -0
- microdot-2.5.0/docs/extensions/production.rst +120 -0
- microdot-2.5.0/docs/extensions/sessions.rst +68 -0
- microdot-2.5.0/docs/extensions/sse.rst +60 -0
- microdot-2.5.0/docs/extensions/templates.rst +123 -0
- microdot-2.5.0/docs/extensions/test_client.rst +36 -0
- microdot-2.5.0/docs/extensions/websocket.rst +63 -0
- microdot-2.5.0/docs/implementation/index.rst +11 -0
- {microdot-2.4.0 → microdot-2.5.0}/docs/index.rst +6 -5
- microdot-2.5.0/docs/intro.rst +44 -0
- microdot-2.5.0/docs/users-guide/concurrency.rst +36 -0
- microdot-2.5.0/docs/users-guide/defining-routes.rst +378 -0
- microdot-2.5.0/docs/users-guide/index.rst +18 -0
- microdot-2.5.0/docs/users-guide/intro.rst +160 -0
- microdot-2.5.0/docs/users-guide/request-object.rst +169 -0
- microdot-2.5.0/docs/users-guide/responses.rst +200 -0
- {microdot-2.4.0 → microdot-2.5.0}/pyproject.toml +2 -1
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/__init__.py +1 -1
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/asgi.py +35 -2
- microdot-2.5.0/src/microdot/csrf.py +122 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/login.py +15 -6
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/microdot.py +16 -5
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/session.py +2 -1
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/test_client.py +8 -4
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/wsgi.py +2 -1
- {microdot-2.4.0 → microdot-2.5.0/src/microdot.egg-info}/PKG-INFO +7 -19
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot.egg-info/SOURCES.txt +39 -4
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot.egg-info/requires.txt +1 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/__init__.py +1 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_asgi.py +100 -0
- microdot-2.5.0/tests/test_csrf.py +298 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_login.py +3 -1
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_microdot.py +24 -0
- {microdot-2.4.0 → microdot-2.5.0}/tox.ini +2 -1
- microdot-2.4.0/docs/api.rst +0 -95
- microdot-2.4.0/docs/extensions.rst +0 -778
- microdot-2.4.0/docs/intro.rst +0 -986
- {microdot-2.4.0 → microdot-2.5.0}/LICENSE +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/MANIFEST.in +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/docs/Makefile +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/docs/_static/css/custom.css +0 -0
- {microdot-2.4.0/docs → microdot-2.5.0/docs/implementation}/freezing.rst +0 -0
- {microdot-2.4.0/docs → microdot-2.5.0/docs/implementation}/migrating.rst +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/docs/make.bat +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/setup.cfg +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/auth.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/cors.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/helpers.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/jinja.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/multipart.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/sse.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/utemplate.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot/websocket.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot.egg-info/dependency_links.txt +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot.egg-info/not-zip-safe +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/src/microdot.egg-info/top_level.txt +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.bin +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.css +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.gif +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.gz +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.html +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.jpg +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.js +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.json +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.png +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.txt +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/files/test.txt.gz +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/mock_socket.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/templates/hello.jinja.txt +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/templates/hello.utemplate.txt +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/templates/hello_utemplate_txt.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_auth.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_cors.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_end2end.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_jinja.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_multidict.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_multipart.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_request.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_response.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_session.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_sse.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_url_pattern.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_urlencode.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_utemplate.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_websocket.py +0 -0
- {microdot-2.4.0 → microdot-2.5.0}/tests/test_wsgi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microdot
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: The impossibly small web framework for MicroPython
|
|
5
5
|
Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/miguelgrinberg/microdot
|
|
@@ -18,6 +18,7 @@ Provides-Extra: dev
|
|
|
18
18
|
Requires-Dist: tox; extra == "dev"
|
|
19
19
|
Provides-Extra: docs
|
|
20
20
|
Requires-Dist: sphinx; extra == "docs"
|
|
21
|
+
Requires-Dist: furo; extra == "docs"
|
|
21
22
|
Requires-Dist: pyjwt; extra == "docs"
|
|
22
23
|
Dynamic: license-file
|
|
23
24
|
|
|
@@ -42,24 +43,15 @@ async def index(request):
|
|
|
42
43
|
app.run()
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
## Migrating to Microdot 2
|
|
46
|
-
|
|
47
|
-
Version 2 of Microdot incorporates feedback received from users of earlier
|
|
48
|
-
releases, and attempts to improve and correct some design decisions that have
|
|
49
|
-
proven to be problematic.
|
|
50
|
-
|
|
51
|
-
For this reason most applications built for earlier versions will need to be
|
|
52
|
-
updated to work correctly with Microdot 2. The
|
|
53
|
-
[Migration Guide](https://microdot.readthedocs.io/en/stable/migrating.html)
|
|
54
|
-
describes the backwards incompatible changes that were made.
|
|
55
|
-
|
|
56
46
|
## Resources
|
|
57
47
|
|
|
58
48
|
- [Change Log](https://github.com/miguelgrinberg/microdot/blob/main/CHANGES.md)
|
|
59
49
|
- Documentation
|
|
60
50
|
- [Latest](https://microdot.readthedocs.io/en/latest/)
|
|
61
51
|
- [Stable (v2)](https://microdot.readthedocs.io/en/stable/)
|
|
62
|
-
|
|
52
|
+
- Legacy (v1)
|
|
53
|
+
- [Code](https://github.com/miguelgrinberg/microdot/tree/v1)
|
|
54
|
+
- [Documentation](https://microdot.readthedocs.io/en/v1/)
|
|
63
55
|
|
|
64
56
|
## Roadmap
|
|
65
57
|
|
|
@@ -68,12 +60,8 @@ MicroPython and CPython:
|
|
|
68
60
|
|
|
69
61
|
- Authentication support, similar to [Flask-Login](https://github.com/maxcountryman/flask-login) for Flask (**Added in version 2.1**)
|
|
70
62
|
- Support for forms encoded in `multipart/form-data` format (**Added in version 2.2**)
|
|
63
|
+
- CSRF protection extension
|
|
64
|
+
- Pub/sub mini-framework for WebSocket and SSE
|
|
71
65
|
- OpenAPI integration, similar to [APIFairy](https://github.com/miguelgrinberg/apifairy) for Flask
|
|
72
66
|
|
|
73
|
-
In addition to the above, the following extensions are also under consideration,
|
|
74
|
-
but only for CPython:
|
|
75
|
-
|
|
76
|
-
- Database integration through [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy)
|
|
77
|
-
- Socket.IO support through [python-socketio](https://github.com/miguelgrinberg/python-socketio)
|
|
78
|
-
|
|
79
67
|
Do you have other ideas to propose? Let's [discuss them](https://github.com/:miguelgrinberg/microdot/discussions/new?category=ideas)!
|
|
@@ -19,24 +19,15 @@ async def index(request):
|
|
|
19
19
|
app.run()
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
## Migrating to Microdot 2
|
|
23
|
-
|
|
24
|
-
Version 2 of Microdot incorporates feedback received from users of earlier
|
|
25
|
-
releases, and attempts to improve and correct some design decisions that have
|
|
26
|
-
proven to be problematic.
|
|
27
|
-
|
|
28
|
-
For this reason most applications built for earlier versions will need to be
|
|
29
|
-
updated to work correctly with Microdot 2. The
|
|
30
|
-
[Migration Guide](https://microdot.readthedocs.io/en/stable/migrating.html)
|
|
31
|
-
describes the backwards incompatible changes that were made.
|
|
32
|
-
|
|
33
22
|
## Resources
|
|
34
23
|
|
|
35
24
|
- [Change Log](https://github.com/miguelgrinberg/microdot/blob/main/CHANGES.md)
|
|
36
25
|
- Documentation
|
|
37
26
|
- [Latest](https://microdot.readthedocs.io/en/latest/)
|
|
38
27
|
- [Stable (v2)](https://microdot.readthedocs.io/en/stable/)
|
|
39
|
-
|
|
28
|
+
- Legacy (v1)
|
|
29
|
+
- [Code](https://github.com/miguelgrinberg/microdot/tree/v1)
|
|
30
|
+
- [Documentation](https://microdot.readthedocs.io/en/v1/)
|
|
40
31
|
|
|
41
32
|
## Roadmap
|
|
42
33
|
|
|
@@ -45,12 +36,8 @@ MicroPython and CPython:
|
|
|
45
36
|
|
|
46
37
|
- Authentication support, similar to [Flask-Login](https://github.com/maxcountryman/flask-login) for Flask (**Added in version 2.1**)
|
|
47
38
|
- Support for forms encoded in `multipart/form-data` format (**Added in version 2.2**)
|
|
39
|
+
- CSRF protection extension
|
|
40
|
+
- Pub/sub mini-framework for WebSocket and SSE
|
|
48
41
|
- OpenAPI integration, similar to [APIFairy](https://github.com/miguelgrinberg/apifairy) for Flask
|
|
49
42
|
|
|
50
|
-
In addition to the above, the following extensions are also under consideration,
|
|
51
|
-
but only for CPython:
|
|
52
|
-
|
|
53
|
-
- Database integration through [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy)
|
|
54
|
-
- Socket.IO support through [python-socketio](https://github.com/miguelgrinberg/python-socketio)
|
|
55
|
-
|
|
56
43
|
Do you have other ideas to propose? Let's [discuss them](https://github.com/:miguelgrinberg/microdot/discussions/new?category=ideas)!
|
|
@@ -46,7 +46,8 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
|
46
46
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
47
47
|
# a list of builtin themes.
|
|
48
48
|
#
|
|
49
|
-
html_theme = '
|
|
49
|
+
html_theme = 'furo'
|
|
50
|
+
html_title = 'Microdot'
|
|
50
51
|
|
|
51
52
|
# Add any paths that contain custom static files (such as style sheets) here,
|
|
52
53
|
# relative to this directory. They are copied after the builtin static files,
|
|
@@ -58,12 +59,6 @@ html_css_files = [
|
|
|
58
59
|
]
|
|
59
60
|
|
|
60
61
|
html_theme_options = {
|
|
61
|
-
'github_user': 'miguelgrinberg',
|
|
62
|
-
'github_repo': 'microdot',
|
|
63
|
-
'github_banner': True,
|
|
64
|
-
'github_button': True,
|
|
65
|
-
'github_type': 'star',
|
|
66
|
-
'fixed_sidebar': True,
|
|
67
62
|
}
|
|
68
63
|
|
|
69
64
|
autodoc_default_options = {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Authentication
|
|
2
|
+
~~~~~~~~~~~~~~
|
|
3
|
+
|
|
4
|
+
.. list-table::
|
|
5
|
+
:align: left
|
|
6
|
+
|
|
7
|
+
* - Compatibility
|
|
8
|
+
- | CPython & MicroPython
|
|
9
|
+
|
|
10
|
+
* - Required Microdot source files
|
|
11
|
+
- | `auth.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot/auth.py>`_
|
|
12
|
+
|
|
13
|
+
* - Required external dependencies
|
|
14
|
+
- | None
|
|
15
|
+
|
|
16
|
+
* - Examples
|
|
17
|
+
- | `basic_auth.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/auth/basic_auth.py>`_
|
|
18
|
+
| `token_auth.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/auth/token_auth.py>`_
|
|
19
|
+
|
|
20
|
+
The authentication extension provides helper classes for two commonly used
|
|
21
|
+
authentication patterns, described below.
|
|
22
|
+
|
|
23
|
+
Basic Authentication
|
|
24
|
+
^^^^^^^^^^^^^^^^^^^^
|
|
25
|
+
|
|
26
|
+
`Basic Authentication <https://en.wikipedia.org/wiki/Basic_access_authentication>`_
|
|
27
|
+
is a method of authentication that is part of the HTTP specification. It allows
|
|
28
|
+
clients to authenticate to a server using a username and a password. Web
|
|
29
|
+
browsers have native support for Basic Authentication and will automatically
|
|
30
|
+
prompt the user for a username and a password when a protected resource is
|
|
31
|
+
accessed.
|
|
32
|
+
|
|
33
|
+
To use Basic Authentication, create an instance of the :class:`BasicAuth <microdot.auth.BasicAuth>`
|
|
34
|
+
class::
|
|
35
|
+
|
|
36
|
+
from microdot.auth import BasicAuth
|
|
37
|
+
|
|
38
|
+
auth = BasicAuth(app)
|
|
39
|
+
|
|
40
|
+
Next, create an authentication function. The function must accept a request
|
|
41
|
+
object and a username and password pair provided by the user. If the
|
|
42
|
+
credentials are valid, the function must return an object that represents the
|
|
43
|
+
user. If the authentication function cannot validate the user provided
|
|
44
|
+
credentials it must return ``None``. Decorate the function with
|
|
45
|
+
``@auth.authenticate``::
|
|
46
|
+
|
|
47
|
+
@auth.authenticate
|
|
48
|
+
async def verify_user(request, username, password):
|
|
49
|
+
user = await load_user_from_database(username)
|
|
50
|
+
if user and user.verify_password(password):
|
|
51
|
+
return user
|
|
52
|
+
|
|
53
|
+
To protect a route with authentication, add the ``auth`` instance as a
|
|
54
|
+
decorator::
|
|
55
|
+
|
|
56
|
+
@app.route('/')
|
|
57
|
+
@auth
|
|
58
|
+
async def index(request):
|
|
59
|
+
return f'Hello, {request.g.current_user}!'
|
|
60
|
+
|
|
61
|
+
While running an authenticated request, the user object returned by the
|
|
62
|
+
authenticaction function is accessible as ``request.g.current_user``.
|
|
63
|
+
|
|
64
|
+
If an endpoint is intended to work with or without authentication, then it can
|
|
65
|
+
be protected with the ``auth.optional`` decorator::
|
|
66
|
+
|
|
67
|
+
@app.route('/')
|
|
68
|
+
@auth.optional
|
|
69
|
+
async def index(request):
|
|
70
|
+
if request.g.current_user:
|
|
71
|
+
return f'Hello, {request.g.current_user}!'
|
|
72
|
+
else:
|
|
73
|
+
return 'Hello, anonymous user!'
|
|
74
|
+
|
|
75
|
+
As shown in the example, a route can check ``request.g.current_user`` to
|
|
76
|
+
determine if the user is authenticated or not.
|
|
77
|
+
|
|
78
|
+
Token Authentication
|
|
79
|
+
^^^^^^^^^^^^^^^^^^^^
|
|
80
|
+
|
|
81
|
+
To set up token authentication, create an instance of
|
|
82
|
+
:class:`TokenAuth <microdot.auth.TokenAuth>`::
|
|
83
|
+
|
|
84
|
+
from microdot.auth import TokenAuth
|
|
85
|
+
|
|
86
|
+
auth = TokenAuth()
|
|
87
|
+
|
|
88
|
+
Then add a function that verifies the token and returns the user it belongs to,
|
|
89
|
+
or ``None`` if the token is invalid or expired::
|
|
90
|
+
|
|
91
|
+
@auth.authenticate
|
|
92
|
+
async def verify_token(request, token):
|
|
93
|
+
return load_user_from_token(token)
|
|
94
|
+
|
|
95
|
+
As with Basic authentication, the ``auth`` instance is used as a decorator to
|
|
96
|
+
protect your routes, and the authenticated user is accessible from the request
|
|
97
|
+
object as ``request.g.current_user``::
|
|
98
|
+
|
|
99
|
+
@app.route('/')
|
|
100
|
+
@auth
|
|
101
|
+
async def index(request):
|
|
102
|
+
return f'Hello, {request.g.current_user}!'
|
|
103
|
+
|
|
104
|
+
Optional authentication can also be used with tokens::
|
|
105
|
+
|
|
106
|
+
@app.route('/')
|
|
107
|
+
@auth.optional
|
|
108
|
+
async def index(request):
|
|
109
|
+
if request.g.current_user:
|
|
110
|
+
return f'Hello, {request.g.current_user}!'
|
|
111
|
+
else:
|
|
112
|
+
return 'Hello, anonymous user!'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Cross-Origin Resource Sharing (CORS)
|
|
2
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
3
|
+
|
|
4
|
+
.. list-table::
|
|
5
|
+
:align: left
|
|
6
|
+
|
|
7
|
+
* - Compatibility
|
|
8
|
+
- | CPython & MicroPython
|
|
9
|
+
|
|
10
|
+
* - Required Microdot source files
|
|
11
|
+
- | `cors.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot/cors.py>`_
|
|
12
|
+
|
|
13
|
+
* - Required external dependencies
|
|
14
|
+
- | None
|
|
15
|
+
|
|
16
|
+
* - Examples
|
|
17
|
+
- | `app.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/cors/app.py>`_
|
|
18
|
+
|
|
19
|
+
The CORS extension provides support for `Cross-Origin Resource Sharing
|
|
20
|
+
(CORS) <https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS>`_. CORS is a
|
|
21
|
+
mechanism that allows web applications running on different origins to access
|
|
22
|
+
resources from each other. For example, a web application running on
|
|
23
|
+
``https://example.com`` can access resources from ``https://api.example.com``.
|
|
24
|
+
|
|
25
|
+
To enable CORS support, create an instance of the
|
|
26
|
+
:class:`CORS <microdot.cors.CORS>` class and configure the desired options.
|
|
27
|
+
Example::
|
|
28
|
+
|
|
29
|
+
from microdot import Microdot
|
|
30
|
+
from microdot.cors import CORS
|
|
31
|
+
|
|
32
|
+
app = Microdot()
|
|
33
|
+
cors = CORS(app, allowed_origins=['https://example.com'],
|
|
34
|
+
allow_credentials=True)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Cross-Site Request Forgery (CSRF) Protection
|
|
2
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
3
|
+
|
|
4
|
+
.. list-table::
|
|
5
|
+
:align: left
|
|
6
|
+
|
|
7
|
+
* - Compatibility
|
|
8
|
+
- | CPython & MicroPython
|
|
9
|
+
|
|
10
|
+
* - Required Microdot source files
|
|
11
|
+
- | `csrf.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot/csrf.py>`_
|
|
12
|
+
|
|
13
|
+
* - Required external dependencies
|
|
14
|
+
- | None
|
|
15
|
+
|
|
16
|
+
* - Examples
|
|
17
|
+
- | `app.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/csrf/app.py>`_
|
|
18
|
+
|
|
19
|
+
The CSRF extension provides protection against `Cross-Site Request Forgery
|
|
20
|
+
(CSRF) <https://owasp.org/www-community/attacks/csrf>`_ attacks. This
|
|
21
|
+
protection defends against attackers attempting to submit forms or other
|
|
22
|
+
state-changing requests from their own site on behalf of unsuspecting victims,
|
|
23
|
+
while taking advantage of the victims previously established sessions or
|
|
24
|
+
cookies to impersonate them.
|
|
25
|
+
|
|
26
|
+
This extension checks the ``Sec-Fetch-Site`` header sent by all modern web
|
|
27
|
+
browsers to achieve this protection. As a fallback mechanism for older browsers
|
|
28
|
+
that do not support this header, this extension can be linked to the CORS
|
|
29
|
+
extension to validate the ``Origin`` header. If you are interested in the
|
|
30
|
+
details of this protection mechanism, it is described in the
|
|
31
|
+
`OWASP CSRF Prevention Cheat Sheet <https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#fetch-metadata-headers>`_
|
|
32
|
+
page.
|
|
33
|
+
|
|
34
|
+
.. note::
|
|
35
|
+
As of December 2025, OWASP considers the use of Fetch Metadata Headers for
|
|
36
|
+
CSRF protection a
|
|
37
|
+
`defense in depth <https://en.wikipedia.org/wiki/Defence_in_depth>`_
|
|
38
|
+
technique that is insufficient on its own.
|
|
39
|
+
|
|
40
|
+
There is an interesting
|
|
41
|
+
`discussion <https://github.com/OWASP/CheatSheetSeries/issues/1803>`_ on
|
|
42
|
+
this topic in the OWASP GitHub repository where it appears to be agreement
|
|
43
|
+
that this technique provides complete protection for the vast majority of
|
|
44
|
+
use cases. If you are unsure if this method works for your use case, please
|
|
45
|
+
read this discussion to have more context and make the right decision.
|
|
46
|
+
|
|
47
|
+
To enable CSRF protection, create an instance of the
|
|
48
|
+
:class:`CSRF <microdot.csrf.CSRF>` class and configure the desired options.
|
|
49
|
+
Example::
|
|
50
|
+
|
|
51
|
+
from microdot import Microdot
|
|
52
|
+
from microdot.cors import CORS
|
|
53
|
+
from microdot.csrf import CSRF
|
|
54
|
+
|
|
55
|
+
app = Microdot()
|
|
56
|
+
cors = CORS(app, allowed_origins=['https://example.com'])
|
|
57
|
+
csrf = CSRF(app, cors)
|
|
58
|
+
|
|
59
|
+
This will protect all routes that use a state-changing method (``POST``,
|
|
60
|
+
``PUT``, ``PATCH`` or ``DELETE``) and will return a 403 status code response to
|
|
61
|
+
any requests that fail the CSRF check.
|
|
62
|
+
|
|
63
|
+
If there are routes that need to be exempted from the CSRF check, they can be
|
|
64
|
+
decorated with the :meth:`csrf.exempt <microdot.csrf.CSRF.exempt>` decorator::
|
|
65
|
+
|
|
66
|
+
@app.post('/webhook')
|
|
67
|
+
@csrf.exempt
|
|
68
|
+
async def webhook(request):
|
|
69
|
+
# ...
|
|
70
|
+
|
|
71
|
+
For some applications it may be more convenient to have CSRF checks turned off
|
|
72
|
+
by default, and only apply them to explicitly selected routes. In this case,
|
|
73
|
+
pass ``protect_all=False`` when you construct the ``CSRF`` instance and use the
|
|
74
|
+
:meth:`csrf.protect <microdot.csrf.CSRF.protect>` decorator::
|
|
75
|
+
|
|
76
|
+
csrf = CSRF(app, cors, protect_all=False)
|
|
77
|
+
|
|
78
|
+
@app.post('/submit-form')
|
|
79
|
+
@csrf.protect
|
|
80
|
+
async def submit_form(request):
|
|
81
|
+
# ...
|
|
82
|
+
|
|
83
|
+
By default, requests coming from different subdomains are considered to be
|
|
84
|
+
cross-site, and as such they will not pass the CSRF check. If you'd like
|
|
85
|
+
subdomain requests to be considered safe, then set the
|
|
86
|
+
``allow_subdomains=True`` option when you create the ``CSRF`` class.
|
|
87
|
+
|
|
88
|
+
.. note::
|
|
89
|
+
This extension is designed to block requests issued by web browsers when
|
|
90
|
+
they are found to be unsafe or unauthorized by the application owner. The
|
|
91
|
+
method used to determine if a request should be allowed or not is based on
|
|
92
|
+
the value of headers that are only sent by web browsers. Clients other than
|
|
93
|
+
web browsers are not affected by this extension and can send requests
|
|
94
|
+
freely.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Core Extensions
|
|
2
|
+
---------------
|
|
3
|
+
|
|
4
|
+
Microdot is a highly extensible web application framework. The extensions
|
|
5
|
+
described in this section are maintained as part of the Microdot project in
|
|
6
|
+
the same source code repository.
|
|
7
|
+
|
|
8
|
+
.. toctree::
|
|
9
|
+
:maxdepth: 1
|
|
10
|
+
|
|
11
|
+
multipart
|
|
12
|
+
websocket
|
|
13
|
+
sse
|
|
14
|
+
templates
|
|
15
|
+
sessions
|
|
16
|
+
auth
|
|
17
|
+
login
|
|
18
|
+
cors
|
|
19
|
+
csrf
|
|
20
|
+
test_client
|
|
21
|
+
production
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
User Logins
|
|
2
|
+
~~~~~~~~~~~
|
|
3
|
+
|
|
4
|
+
.. list-table::
|
|
5
|
+
:align: left
|
|
6
|
+
|
|
7
|
+
* - Compatibility
|
|
8
|
+
- | CPython & MicroPython
|
|
9
|
+
|
|
10
|
+
* - Required Microdot source files
|
|
11
|
+
- | `login.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot/auth.py>`_
|
|
12
|
+
| `session.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot/session.py>`_
|
|
13
|
+
| `helpers.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot/helpers.py>`_
|
|
14
|
+
* - Required external dependencies
|
|
15
|
+
- | CPython: `PyJWT <https://pyjwt.readthedocs.io/>`_
|
|
16
|
+
| MicroPython: `jwt.py <https://github.com/micropython/micropython-lib/blob/master/python-ecosys/pyjwt/jwt.py>`_,
|
|
17
|
+
`hmac.py <https://github.com/micropython/micropython-lib/blob/master/python-stdlib/hmac/hmac.py>`_
|
|
18
|
+
* - Examples
|
|
19
|
+
- | `login.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/login/login.py>`_
|
|
20
|
+
|
|
21
|
+
The login extension provides user login functionality. The logged in state of
|
|
22
|
+
the user is stored in the user session cookie, and an optional "remember me"
|
|
23
|
+
cookie can also be added to keep the user logged in across browser sessions.
|
|
24
|
+
|
|
25
|
+
To use this extension, create instances of the
|
|
26
|
+
:class:`Session <microdot.session.Session>` and :class:`Login <microdot.login.Login>`
|
|
27
|
+
class::
|
|
28
|
+
|
|
29
|
+
Session(app, secret_key='top-secret!')
|
|
30
|
+
login = Login()
|
|
31
|
+
|
|
32
|
+
The ``Login`` class accept an optional argument with the URL of the login page.
|
|
33
|
+
The default for this URL is */login*.
|
|
34
|
+
|
|
35
|
+
The application must represent users as objects with an ``id`` attribute. A
|
|
36
|
+
function decorated with ``@login.user_loader`` is used to load a user object::
|
|
37
|
+
|
|
38
|
+
@login.user_loader
|
|
39
|
+
async def get_user(user_id):
|
|
40
|
+
return database.get_user(user_id)
|
|
41
|
+
|
|
42
|
+
The application must implement the login form. At the point in which the user
|
|
43
|
+
credentials have been received and verified, a call to the
|
|
44
|
+
:func:`login_user() <microdot.login.Login.login_user>` function must be made to
|
|
45
|
+
record the user in the user session::
|
|
46
|
+
|
|
47
|
+
@app.route('/login', methods=['GET', 'POST'])
|
|
48
|
+
async def login(request):
|
|
49
|
+
# ...
|
|
50
|
+
if user.check_password(password):
|
|
51
|
+
return await login.login_user(request, user, remember=remember_me)
|
|
52
|
+
return redirect('/login')
|
|
53
|
+
|
|
54
|
+
The optional ``remember`` argument is used to add a remember me cookie that
|
|
55
|
+
will log the user in automatically in future sessions. A value of ``True`` will
|
|
56
|
+
keep the log in active for 30 days. Alternatively, an integer number of days
|
|
57
|
+
can be passed in this argument.
|
|
58
|
+
|
|
59
|
+
Any routes that require the user to be logged in must be decorated with
|
|
60
|
+
:func:`@login <microdot.login.Login.__call__>`::
|
|
61
|
+
|
|
62
|
+
@app.route('/')
|
|
63
|
+
@login
|
|
64
|
+
async def index(request):
|
|
65
|
+
# ...
|
|
66
|
+
|
|
67
|
+
Routes that are of a sensitive nature can be decorated with
|
|
68
|
+
:func:`@login.fresh <microdot.login.Login.fresh>`
|
|
69
|
+
instead. This decorator requires that the user has logged in during the current
|
|
70
|
+
session, and will ask the user to logged in again if the session was
|
|
71
|
+
authenticated through a remember me cookie::
|
|
72
|
+
|
|
73
|
+
@app.get('/fresh')
|
|
74
|
+
@login.fresh
|
|
75
|
+
async def fresh(request):
|
|
76
|
+
# ...
|
|
77
|
+
|
|
78
|
+
To log out a user, the :func:`logout_user() <microdot.auth.Login.logout_user>`
|
|
79
|
+
is used::
|
|
80
|
+
|
|
81
|
+
@app.post('/logout')
|
|
82
|
+
@login
|
|
83
|
+
async def logout(request):
|
|
84
|
+
await login.logout_user(request)
|
|
85
|
+
return redirect('/')
|