📝 finish sessions and custom middleware docs

This commit is contained in:
Joe Kaufeld 2024-08-28 17:39:22 -04:00
parent 2f31ad612c
commit d4f8109663
5 changed files with 156 additions and 4 deletions

View File

@ -3,6 +3,7 @@
- [responses](responses.md)
- middleware
- [overview](middleware/overview.md)
- [session](middleware/sessions.md)
- [csrf](middleware/csrf.md)
- [pydantic](middleware/pydantic.md)
- [session](middleware/sessions.md)
- [writing your own](middleware/custom_middleware.md)

View File

@ -16,7 +16,8 @@ app = SpiderwebRouter(
Cross-site request forgery, put simply, is a method for attackers to make legitimate-looking requests in your name to a service or system that you've previously authenticated to. Ways that we can protect against this involve aggressively expiring session cookies, special IDs for forms that are keyed to a specific user, and more.
Notice that in the example above, SessionMiddleware is also included in the middleware list. The CSRF middleware requires the SessionMiddleware to function, and SessionMiddleware must be placed above it in the middleware list.
> [!TIP]
> Notice that in the example above, SessionMiddleware is also included in the middleware list. The CSRF middleware requires the SessionMiddleware to function, and SessionMiddleware must be placed above it in the middleware list.
## CSRF and Forms
@ -52,12 +53,13 @@ def form(request):
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap demo</title>
<title>Form Demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<div class="container">
<h1>Example Form</h1>
<form action="" method="post">
<div class="mb-3">
<input type="email" class="form-control" name="email" id="emailInput" placeholder="name@example.com">

View File

@ -0,0 +1,62 @@
from spiderweb import HttpResponse
# writing your own middleware
Sometimes you want to run the same code on every request or every response (or both!). Lots of processing happens in the middleware layer, and if you want to write your own, all you have to do is write a quick class and put it in a place that Spiderweb can find it. A piece of middleware only needs two things to be successful:
- it must be a class that inherits from SpiderwebMiddleware
- it must handle either requests, responses, or both!
That's really all there is to it. Here's a template you can copy:
```python
from spiderweb.middleware import SpiderwebMiddleware
from spiderweb.request import Request
from spiderweb.response import HttpResponse
class TestMiddleware(SpiderwebMiddleware):
def process_request(self, request: Request) -> None:
# example of a middleware that sets a flag on the request
request.spiderweb = True
def process_response(self, request: Request, response: HttpResponse) -> None:
# example of a middleware that sets a header on the resp
if hasattr(request, "spiderweb"):
response.headers["X-Spiderweb"] = "true"
```
Middleware is run twice: once for the incoming request and once for the outgoing response. You only need to include whichever function is required for the functionality you need.
## process_request(self, request):
`process_request` is called before the view is reached in the execution order. You will receive the assembled Request object, and any middleware declared above this one will have already run. Because the request is the single instantiation of a class, you can modify it in-place without returning anything and your changes will stick.
This function also has a special ability; it can stop execution before the view is called by returning a response. If a response is returned, Spiderweb will immediately skip to applying the response middleware and sending the response back to the client. Here's an example of what that might look like:
```python
class JohnMiddleware(SpiderwebMiddleware):
def process_request(self, request: Request) -> Optional[HttpResponse]:
if (
hasattr(request, "user")
and user.name == "John"
and request.path.startswith("/admin")
):
return HttpResponse("Go away, John!", status_code=403)
```
In this case, if the user John tries to access any route that starts with "/admin", he'll immediately get denied and the view will never be called. If the request does not have a user attached to it (or the user is not John), then the middleware will return None and Spiderweb will continue processing.
## process_response(self, request, response):
This function is called after the view has run and returned a response. You will receive the request object and the response object; like with the request object, the response is also a single instantiation of a class, so any changes you make will stick automatically.
Unlike `process_request`, returning a value here doesn't change anything. We're already processing a request, and there are opportunities to turn away requests / change the response at both the `process_request` layer and the view layer, so Spiderweb assumes that whatever it is working on here is what you mean to return to the user. The response object that you receive in the middleware is still prerendered, so any changes you make to it will take effect after it finishes the middleware and renders the response.
## UnusedMiddleware
```python
from spiderweb.exceptions import UnusedMiddleware
```
If you don't want your middleware to run for some reason, either `process_request` or `process_response` can raise the UnusedMiddleware exception. If this happens, Spiderweb will kick your middleware out of the processing order for the rest of the life of the server. Note that this applies to the middleware as a whole, so both functions will not be run if an UnusedMiddleware is raised. This is a great way to mark debug middleware that shouldn't run or create time-delay middleware that runs until a certain condition is met!

View File

@ -0,0 +1,87 @@
# sessions middleware
```python
from spiderweb import SpiderwebRouter
app = SpiderwebRouter(
middleware=[
"spiderweb.middleware.sessions.SessionMiddleware",
],
)
```
Arguably one of the more important things that a server-side web framework can do, besides take in requests and serve responses, is keep track of folks as they navigate your website. That's what the sessions middleware is for!
Visitors are assigned a random value when they visit for the first time, and that value will follow them around until it either expires or it's deleted. The total amount of time that it's around is configurable, as are the various settings for the session cookie.
## request.SESSION
When the sessions middleware is enabled, the request object will have a new attribute labeled `SESSION`. This is a dictionary, and you can put pretty much anything you want in it as long as its serializable to JSON! When the user visits again with an active session, the data will automatically be available on the `SESSION` object again. Here's an example of a complete server using sessions:
```python
from spiderweb import SpiderwebRouter, HttpResponse
app = SpiderwebRouter(
middleware=["spiderweb.middleware.sessions.SessionMiddleware"],
)
@app.route("/")
def session(request):
if "val" not in request.SESSION:
request.SESSION["val"] = 0
else:
request.SESSION["val"] += 1
return HttpResponse(body=f"Session value: {request.SESSION['val']}")
if __name__ == "__main__":
app.start()
```
If you drop this into a new file and call it with `python yourfile.py`, you should see two things:
- there is a new file created called `spiderweb.db`
- if you open your browser and navigate to http://localhost:8000 and refresh the page a few times, the number should increment
Use the session object to keep track of anything you need to!
> Read more [about the database here!](../db.md)
## Settings
There are a few configurable things with the settings middleware, and they all have to do with the cookie itself.
```python
app = SpiderwebRouter(
session_cookie_name="swsession",
session_cookie_secure=False,
session_cookie_http_only=True,
session_cookie_same_site="lax",
session_cookie_path="/",
)
```
### session_cookie_name
Any valid cookie name is acceptable here; the default is `swsession`. You can [read more about valid names for cookies here][cookienames].
### session_cookie_secure
This marks that the cookie will only be sent back to the server with a valid HTTPS session. By default, this is set to `False`, but should be manually set to `True` if the server is deployed.
### session_cookie_http_only
This marks whether the session cookie will have the `HttpOnly` attribute. This makes it invisible to client-side javascript. The default is `False`.
### session_cookie_same_site
There are three valid values for this: "strict", "lax", and "none".
- `strict`: the browser will only send the cookie when the user performs a request on the same site that sent the cookie, and notably not on the first request to the server when navigating to the site from a different origin.
- `lax`: the browser will send the cookie when the user performs a request on the same site that sent the cookie, and also on the first request to the server when navigating to the site from a different origin. This is the default setting.
- `none`: the browser will send the cookie regardless of the origin of the request. However, you must also set `session_cookie_secure` to `True` if you want to use this setting, otherwise the browser will refuse to send it.
### session_cookie_path
This is the path that the cookie is valid for. By default, it's set to `/`, which means that the cookie is valid for the entire domain. If you want to restrict the cookie to a specific path, you can set it here.
[cookienames]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes

View File

@ -1,7 +1,7 @@
from datetime import datetime, timedelta
import json
from peewee import CharField, TextField, DateTimeField, BooleanField
from peewee import CharField, TextField, DateTimeField
from spiderweb.middleware import SpiderwebMiddleware
from spiderweb.request import Request