1
+ import datetime as dt
1
2
import logging
2
3
from typing import TYPE_CHECKING , cast
3
4
4
5
from django .core .exceptions import SuspiciousOperation
5
6
from msgspec import MsgspecError
6
7
8
+ from lib .http_cookies_helpers import (
9
+ HttpCookieAttributes ,
10
+ set_http_cookie_on_django_response ,
11
+ )
12
+
7
13
from .authentication import LichessTokenRetrievalProcessContext
8
- from .models import LICHESS_ACCESS_TOKEN_PREFIX
14
+ from .lichess_api import is_lichess_api_access_token_valid
9
15
10
16
if TYPE_CHECKING :
11
17
from django .http import HttpRequest , HttpResponse
12
18
13
19
from .authentication import LichessToken
14
20
from .models import LichessAccessToken
15
21
16
- _OAUTH2_TOKEN_RETRIEVAL_CONTEXT_COOKIE = {
17
- "name" : "lichess.oauth2.ctx" ,
18
- # One day should be more than enough to let the user grant their authorisation:
19
- "max-age" : 3600 * 24 ,
20
- }
21
-
22
- _API_ACCESS_TOKEN_COOKIE = {
23
- "name" : "lichess.access_token" ,
22
+ _OAUTH2_TOKEN_RETRIEVAL_CONTEXT_COOKIE_ATTRS = HttpCookieAttributes (
23
+ name = "lichess.oauth2.ctx" ,
24
+ # This cookie only has to be valid while the user is redirected to Lichess
25
+ # and press the "Authorize" button there.
26
+ max_age = dt .timedelta (hours = 1 ),
27
+ http_only = True ,
28
+ same_site = "Lax" ,
29
+ )
30
+
31
+ _API_ACCESS_TOKEN_COOKIE_ATTRS = HttpCookieAttributes (
32
+ name = "lichess.access_token" ,
24
33
# Access tokens delivered by Lichess "are long-lived (expect one year)".
25
- # Let's store them for approximately 6 months, on our end:
26
- "max-age" : 3600 * 24 * 30 * 6 ,
27
- }
34
+ # As Lichess gives us the expiry date of the tokens it gives us, we can use that
35
+ # for our own cookie - so no "max-age" entry here, but we'll specify one at runtime.
36
+ max_age = None ,
37
+ http_only = True ,
38
+ same_site = "Lax" ,
39
+ )
28
40
29
41
30
42
_logger = logging .getLogger (__name__ )
@@ -37,12 +49,10 @@ def store_oauth2_token_retrieval_context_in_response_cookie(
37
49
Store OAuth2 token retrieval context into a short-lived response cookie.
38
50
"""
39
51
40
- response .set_cookie (
41
- _OAUTH2_TOKEN_RETRIEVAL_CONTEXT_COOKIE ["name" ],
42
- context .to_cookie_content (),
43
- max_age = _OAUTH2_TOKEN_RETRIEVAL_CONTEXT_COOKIE ["max-age" ],
44
- httponly = True ,
45
- samesite = "Lax" ,
52
+ set_http_cookie_on_django_response (
53
+ response = response ,
54
+ attributes = _OAUTH2_TOKEN_RETRIEVAL_CONTEXT_COOKIE_ATTRS ,
55
+ value = context .to_cookie_content (),
46
56
)
47
57
48
58
@@ -53,7 +63,7 @@ def get_oauth2_token_retrieval_context_from_request(
53
63
Returns a context created from the "CSRF state" and "code verifier" found in the request's cookies.
54
64
"""
55
65
cookie_content : str | None = request .COOKIES .get (
56
- _OAUTH2_TOKEN_RETRIEVAL_CONTEXT_COOKIE [ " name" ]
66
+ _OAUTH2_TOKEN_RETRIEVAL_CONTEXT_COOKIE_ATTRS . name
57
67
)
58
68
if not cookie_content :
59
69
return None
@@ -73,7 +83,7 @@ def get_oauth2_token_retrieval_context_from_request(
73
83
def delete_oauth2_token_retrieval_context_from_cookies (
74
84
response : "HttpResponse" ,
75
85
) -> None :
76
- response .delete_cookie (_OAUTH2_TOKEN_RETRIEVAL_CONTEXT_COOKIE [ " name" ] )
86
+ response .delete_cookie (_OAUTH2_TOKEN_RETRIEVAL_CONTEXT_COOKIE_ATTRS . name )
77
87
78
88
79
89
def store_lichess_api_access_token_in_response_cookie (
@@ -82,15 +92,17 @@ def store_lichess_api_access_token_in_response_cookie(
82
92
"""
83
93
Store a Lichess API token into a long-lived response cookie.
84
94
"""
95
+ # TODO: use a secured cookie here?
96
+
97
+ # Our cookie will expire when the access token given by Lichess will:
98
+ cookie_attributes = _API_ACCESS_TOKEN_COOKIE_ATTRS ._replace (
99
+ max_age = dt .timedelta (seconds = token .expires_in ),
100
+ )
85
101
86
- response .set_cookie (
87
- _API_ACCESS_TOKEN_COOKIE ["name" ],
88
- token .access_token ,
89
- # TODO: should we use the token's `expires_in` here, rather than our custom
90
- # expiry period? There are pros and cons, let's decide that later :-)
91
- max_age = _API_ACCESS_TOKEN_COOKIE ["max-age" ],
92
- httponly = True ,
93
- samesite = "Lax" ,
102
+ set_http_cookie_on_django_response (
103
+ response = response ,
104
+ attributes = cookie_attributes ,
105
+ value = token .access_token ,
94
106
)
95
107
96
108
@@ -100,14 +112,13 @@ def get_lichess_api_access_token_from_request(
100
112
"""
101
113
Returns a Lichess API token found in the request's cookies.
102
114
"""
103
- cookie_content : str | None = request .COOKIES .get (_API_ACCESS_TOKEN_COOKIE ["name" ])
115
+ cookie_content : str | None = request .COOKIES .get (
116
+ _API_ACCESS_TOKEN_COOKIE_ATTRS .name
117
+ )
104
118
if not cookie_content :
105
119
return None
106
120
107
- if (
108
- not cookie_content .startswith (LICHESS_ACCESS_TOKEN_PREFIX )
109
- or len (cookie_content ) < 10
110
- ):
121
+ if not is_lichess_api_access_token_valid (cookie_content ):
111
122
raise SuspiciousOperation (
112
123
f"Suspicious Lichess API token value '{ cookie_content } '"
113
124
)
@@ -118,4 +129,4 @@ def get_lichess_api_access_token_from_request(
118
129
def delete_lichess_api_access_token_from_cookies (
119
130
response : "HttpResponse" ,
120
131
) -> None :
121
- response .delete_cookie (_API_ACCESS_TOKEN_COOKIE [ " name" ] )
132
+ response .delete_cookie (_API_ACCESS_TOKEN_COOKIE_ATTRS . name )
0 commit comments