Skip to content

Commit 66aa20b

Browse files
authored
Merge pull request #25 from LiberalArtist/id-cookie-shelf-life
Adds a `#:shelf-life` argument to `valid-id-cookie?`
2 parents dc9c00e + 626ad02 commit 66aa20b

File tree

3 files changed

+62
-27
lines changed

3 files changed

+62
-27
lines changed

web-server-doc/web-server/scribblings/http.scrbl

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ This module provides functions for creating and verifying
344344
authenticated cookies that are intrinsically timestamped. It is based
345345
on the algorithm proposed by the
346346
@link["https://pdos.csail.mit.edu/archive/cookies/"]{MIT Cookie Eaters}: if you store
347-
the data @racket[_data] at thime @racket[_authored-seconds], then the
347+
the data @racket[_data] at time @racket[_authored-seconds], then the
348348
user will receive @litchar{digest&authored-seconds&data}, where
349349
@racket[_digest] is an HMAC-SHA1 digest of @racket[_authored-seconds]
350350
and @racket[_data], using an arbitrary secret key. When you receive a
@@ -402,32 +402,51 @@ available (@racket[make-secret-salt/file]),
402402
@defproc*[([(request-id-cookie [request request?]
403403
[#:name name (and/c string? cookie-name?)]
404404
[#:key secret-salt bytes?]
405-
[#:timeout timeout number? +inf.0])
405+
[#:timeout timeout real? +inf.0]
406+
[#:shelf-life shelf-life real? +inf.0])
406407
(or/c #f (and/c string? cookie-value?))]
407408
[(request-id-cookie [name (and/c string? cookie-name?)]
408409
[secret-salt bytes?]
409410
[request request?]
410-
[#:timeout timeout number? +inf.0])
411+
[#:timeout timeout number? +inf.0]
412+
[#:shelf-life shelf-life real? +inf.0])
411413
(or/c #f (and/c string? cookie-value?))])]{
412414
Extracts the first authenticated cookie named @racket[name]
413415
that was previously signed with @racket[secret-salt]
414-
before @racket[timeout] from @racket[request].
416+
from @racket[request], with the allowable age of the cookie
417+
is controlled by @racket[shelf-life] and @racket[timeout] as with
418+
@racket[valid-id-cookie?].
419+
415420
If no valid cookie is available, returns @racket[#f].
416421
}
417422

418423
@defproc[(valid-id-cookie? [cookie any/c]
419424
[#:name name (and/c string? cookie-name?)]
420425
[#:key secret-salt bytes?]
421-
[#:timeout timeout number? +inf.0])
426+
[#:timeout timeout number? +inf.0]
427+
[#:shelf-life shelf-life real? +inf.0])
422428
(or/c #f (and/c string? cookie-value?))]{
423429
Recognizes authenticated cookies named @racket[name] that were
424-
previously signed with @racket[secret-salt]
425-
before @racket[timeout]. Values satisfying either @racket[cookie?]
430+
previously signed with @racket[secret-salt].
431+
Values satisfying either @racket[cookie?]
426432
or @racket[client-cookie?] can be recognized.
427433

434+
The @racket[shelf-life] specifies the maximum age of the cookie
435+
in seconds. Cookies created more than @racket[shelf-life] seconds
436+
ago will not be considered valid.
437+
The default value, @racket[+inf.0], permits all properly named and
438+
signed cookies.
439+
440+
Counterintuitively,
441+
the @racket[timeout] argument requires that the cookie have been
442+
created @italic{before} a certain moment in time: in other words,
443+
it requires that the cookie be @italic{older} than a certain age.
444+
This is not usually what you want to restrict.
428445
Specifically, @racket[valid-id-cookie?] tests that
429446
@racket[(authored . <= . timeout)], where @racket[authored] is the
430447
value returned by @racket[(current-seconds)] when the cookie was created.
448+
The default value, @racket[+inf.0], permits all properly named and
449+
signed cookies.
431450
}
432451

433452
@defproc[(logout-id-cookie [name cookie-name?]

web-server-lib/web-server/http/id-cookie.rkt

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
(->* [any/c
2727
#:name (and/c string? cookie-name?)
2828
#:key bytes?]
29-
[#:timeout number?]
29+
[#:timeout real?
30+
#:shelf-life real?]
3031
(or/c #f (and/c string? cookie-value?)))]
3132
[request-id-cookie
3233
(->i ([name-or-req {kw-name}
@@ -37,7 +38,8 @@
3738
[maybe-req request?]
3839
#:name [kw-name (and/c string? cookie-name?)]
3940
#:key [kw-key bytes?]
40-
#:timeout [timeout number?])
41+
#:timeout [timeout number?]
42+
#:shelf-life [shelf-life real?])
4143
#:pre/desc {maybe-key maybe-req kw-name kw-key}
4244
(let ([maybe-key/un (unsupplied-arg? maybe-key)]
4345
[maybe-req/un (unsupplied-arg? maybe-req)]
@@ -131,18 +133,21 @@
131133
(define (valid-id-cookie? c
132134
#:name name
133135
#:key key
134-
#:timeout [timeout +inf.0])
136+
#:timeout [timeout +inf.0]
137+
#:shelf-life [shelf-life +inf.0])
135138
(and (id-cookie? name c)
136139
(with-handlers ([exn:fail? (lambda (x) #f)])
137140
(match (if (client-cookie? c)
138141
(client-cookie-value c)
139142
(cookie-value c))
140-
[(regexp #rx"^(.+)&(.+)&(.*)$"
141-
(list _
142-
digest
143-
(app string->number authored)
144-
data))
145-
(and (authored . <= . timeout)
143+
[(pregexp #px"^(.+)&(\\d+)&(.*)$"
144+
(list _
145+
digest
146+
(app string->number authored)
147+
data))
148+
(and [authored . <= . timeout]
149+
[shelf-life . >= . (- (current-seconds)
150+
authored)]
146151
(let ([re-digest (mac key (list authored data))])
147152
(string=? digest re-digest))
148153
data)]
@@ -154,15 +159,17 @@
154159
[maybe-req #f]
155160
#:name [kw-name #f]
156161
#:key [kw-key #f]
157-
#:timeout [timeout +inf.0])
162+
#:timeout [timeout +inf.0]
163+
#:shelf-life [shelf-life +inf.0])
158164
(let ([name (or kw-name name-or-req)]
159165
[key (or kw-key maybe-key)]
160166
[req (or maybe-req name-or-req)])
161167
(for/or ([c (in-list (request-cookies req))])
162168
(valid-id-cookie? c
163169
#:name name
164170
#:key key
165-
#:timeout timeout))))
171+
#:timeout timeout
172+
#:shelf-life shelf-life))))
166173

167174
(define (logout-id-cookie name
168175
#:path [path #f]
@@ -175,5 +182,3 @@
175182
#f)
176183
#:path path
177184
#:domain domain))
178-
179-

web-server-test/tests/web-server/http/cookies-test.rkt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@
294294
(list (header #"Cookie"
295295
#"my-id-cookie=YmFLLOIDULjpLQOu1+cvMBM+m&1489023629&my-signed-value"))
296296
(delay empty) #f "host" 80 "client"))
297-
(test-not-false "infinite timeout"
297+
(test-not-false "infinite timeout & shelf life"
298298
(request-id-cookie req
299299
#:name "my-id-cookie"
300300
#:key test-secret-salt))
@@ -304,19 +304,30 @@
304304
#:key test-secret-salt
305305
#:timeout (current-seconds)))
306306
(test-not-false "finite timeout / by position"
307-
(request-id-cookie req
308-
#:name "my-id-cookie"
309-
#:key test-secret-salt
310-
#:timeout (current-seconds)))
311-
(test-false "reject expired"
312307
(request-id-cookie "my-id-cookie"
313308
test-secret-salt
314309
req
310+
#:timeout (current-seconds)))
311+
(test-false "timeout / reject expired"
312+
(request-id-cookie req
313+
#:name "my-id-cookie"
314+
#:key test-secret-salt
315315
#:timeout 1089023629))
316+
(test-equal? "long finite shelf-life"
317+
(request-id-cookie req
318+
#:name "my-id-cookie"
319+
#:key test-secret-salt
320+
#:shelf-life 500)
321+
"test-value")
322+
(test-false "shelf-life / reject expired"
323+
(request-id-cookie req
324+
#:name "my-id-cookie"
325+
#:key test-secret-salt
326+
#:shelf-life -10))
316327
))))))
317328

318329

319330
(module+ test
320331
(require rackunit/text-ui)
321332
(run-tests cookies-tests))
322-
333+

0 commit comments

Comments
 (0)