elm-pages 3.0.0-beta.1 → 3.0.0-beta.3
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.
- package/codegen/elm-pages-codegen.js +17 -8
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +285 -101
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
- package/generator/dead-code-review/src/Pages/Review/DeadCodeEliminateData.elm +140 -17
- package/generator/dead-code-review/tests/Pages/Review/DeadCodeEliminateDataTest.elm +218 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
- package/generator/src/SharedTemplate.elm +1 -1
- package/generator/src/generate-template-module-connector.js +0 -28
- package/package.json +1 -2
- package/src/DataSource/File.elm +1 -1
- package/src/DataSource/Internal/Request.elm +0 -5
- package/src/Form/Field.elm +1 -1
- package/src/Form.elm +1 -1
- package/src/Head/Seo.elm +16 -27
- package/src/Pages/Internal/NotFoundReason.elm +3 -2
- package/src/Pages/Internal/Platform/Cli.elm +41 -21
- package/src/Pages/Internal/Platform.elm +3 -3
- package/src/Pages/ProgramConfig.elm +1 -1
- package/src/Server/Session.elm +149 -83
- package/src/Server/SetCookie.elm +89 -31
package/src/Server/Session.elm
CHANGED
|
@@ -1,8 +1,109 @@
|
|
|
1
|
-
module Server.Session exposing
|
|
1
|
+
module Server.Session exposing
|
|
2
|
+
( withSession
|
|
3
|
+
, NotLoadedReason(..)
|
|
4
|
+
, Session, empty, get, insert, remove, update, withFlash
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
{-| You can manage server state with HTTP cookies using this Server.Session API. Server-rendered pages define a `Server.Request.Parser`
|
|
8
|
+
to choose which requests to respond to and how to extract structured data from the incoming request.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Using Sessions in a Request.Parser
|
|
12
|
+
|
|
13
|
+
Using these functions, you can store and read session data in cookies to maintain state between requests.
|
|
14
|
+
For example, TODO:
|
|
15
|
+
|
|
16
|
+
action : RouteParams -> Request.Parser (DataSource (Response ActionData ErrorPage))
|
|
17
|
+
action routeParams =
|
|
18
|
+
MySession.withSession
|
|
19
|
+
(Request.formDataWithServerValidation (form |> Form.initCombinedServer identity))
|
|
20
|
+
(\nameResultData session ->
|
|
21
|
+
nameResultData
|
|
22
|
+
|> DataSource.map
|
|
23
|
+
(\nameResult ->
|
|
24
|
+
case nameResult of
|
|
25
|
+
Err errors ->
|
|
26
|
+
( session
|
|
27
|
+
|> Result.withDefault Nothing
|
|
28
|
+
|> Maybe.withDefault Session.empty
|
|
29
|
+
, Response.render
|
|
30
|
+
{ errors = errors
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
Ok ( _, name ) ->
|
|
35
|
+
( session
|
|
36
|
+
|> Result.withDefault Nothing
|
|
37
|
+
|> Maybe.withDefault Session.empty
|
|
38
|
+
|> Session.insert "name" name
|
|
39
|
+
|> Session.withFlash "message" ("Welcome " ++ name ++ "!")
|
|
40
|
+
, Route.redirectTo Route.Greet
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
The elm-pages framework will manage signing these cookies using the `secrets : DataSource (List String)` you pass in.
|
|
46
|
+
That means that the values you set in your session will be directly visible to anyone who has access to the cookie
|
|
47
|
+
(so don't directly store sensitive data in your session). Since the session cookie is signed using the secret you provide,
|
|
48
|
+
the cookie will be invalidated if it is tampered with because it won't match when elm-pages verifies that it has been
|
|
49
|
+
signed with your secrets. Of course you need to provide secure secrets and treat your secrets with care.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Rotating Secrets
|
|
53
|
+
|
|
54
|
+
The first String in `secrets : DataSource (List String)` will be used to sign sessions, while the remaining String's will
|
|
55
|
+
still be used to attempt to "unsign" the cookies. So if you have a single secret:
|
|
56
|
+
|
|
57
|
+
Session.withSession
|
|
58
|
+
{ name = "mysession"
|
|
59
|
+
, secrets =
|
|
60
|
+
DataSource.map List.singleton
|
|
61
|
+
(Env.expect "SESSION_SECRET2022-09-01")
|
|
62
|
+
, options = cookieOptions
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Then you add a second secret
|
|
66
|
+
|
|
67
|
+
Session.withSession
|
|
68
|
+
{ name = "mysession"
|
|
69
|
+
, secrets =
|
|
70
|
+
DataSource.map2
|
|
71
|
+
(\newSecret oldSecret -> [ newSecret, oldSecret ])
|
|
72
|
+
(Env.expect "SESSION_SECRET2022-12-01")
|
|
73
|
+
(Env.expect "SESSION_SECRET2022-09-01")
|
|
74
|
+
, options = cookieOptions
|
|
75
|
+
}
|
|
2
76
|
|
|
3
|
-
|
|
77
|
+
The new secret (`2022-12-01`) will be used to sign all requests. This API always re-signs using the newest secret in the list
|
|
78
|
+
whenever a new request comes in (even if the Session key-value pairs are unchanged), so these cookies get "refreshed" with the latest
|
|
79
|
+
signing secret when a new request comes in.
|
|
4
80
|
|
|
5
|
-
|
|
81
|
+
However, incoming requests with a cookie signed using the old secret (`2022-09-01`) will still successfully be unsigned
|
|
82
|
+
because they are still in the rotation (and then subsequently "refreshed" and signed using the new secret).
|
|
83
|
+
|
|
84
|
+
This allows you to rotate your session secrets (for security purposes). When a secret goes out of the rotation,
|
|
85
|
+
it will invalidate all cookies signed with that. For example, if we remove our old secret from the rotation:
|
|
86
|
+
|
|
87
|
+
Session.withSession
|
|
88
|
+
{ name = "mysession"
|
|
89
|
+
, secrets =
|
|
90
|
+
DataSource.map List.singleton
|
|
91
|
+
(Env.expect "SESSION_SECRET2022-12-01")
|
|
92
|
+
, options = cookieOptions
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
And then a user makes a request but had a session signed with our old secret (`2022-09-01`), the session will be invalid
|
|
96
|
+
(so `withSession` would parse the session for that request as `Nothing`). It's standard for cookies to have an expiration date,
|
|
97
|
+
so there's nothing wrong with an old session expiring (and the browser will eventually delete old cookies), just be aware of that when rotating secrets.
|
|
98
|
+
|
|
99
|
+
@docs withSession
|
|
100
|
+
|
|
101
|
+
@docs NotLoadedReason
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## Creating and Updating Sessions
|
|
105
|
+
|
|
106
|
+
@docs Session, empty, get, insert, remove, update, withFlash
|
|
6
107
|
|
|
7
108
|
-}
|
|
8
109
|
|
|
@@ -10,10 +111,9 @@ import DataSource exposing (DataSource)
|
|
|
10
111
|
import DataSource.Http
|
|
11
112
|
import DataSource.Internal.Request
|
|
12
113
|
import Dict exposing (Dict)
|
|
13
|
-
import Dict.Extra
|
|
14
114
|
import Json.Decode
|
|
15
115
|
import Json.Encode
|
|
16
|
-
import Server.Request
|
|
116
|
+
import Server.Request
|
|
17
117
|
import Server.Response exposing (Response)
|
|
18
118
|
import Server.SetCookie as SetCookie
|
|
19
119
|
|
|
@@ -23,11 +123,6 @@ type Session
|
|
|
23
123
|
= Session (Dict String Value)
|
|
24
124
|
|
|
25
125
|
|
|
26
|
-
{-| -}
|
|
27
|
-
type alias Decoder decoded =
|
|
28
|
-
Json.Decode.Decoder decoded
|
|
29
|
-
|
|
30
|
-
|
|
31
126
|
{-| -}
|
|
32
127
|
type Value
|
|
33
128
|
= Persistent String
|
|
@@ -113,20 +208,13 @@ empty =
|
|
|
113
208
|
|
|
114
209
|
{-| -}
|
|
115
210
|
type NotLoadedReason
|
|
116
|
-
=
|
|
117
|
-
|
|
|
211
|
+
= NoSessionCookie
|
|
212
|
+
| InvalidSessionCookie
|
|
118
213
|
|
|
119
214
|
|
|
120
215
|
{-| -}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
constructor
|
|
124
|
-
|> Json.Decode.succeed
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
{-| -}
|
|
128
|
-
setValues : Session -> Json.Encode.Value
|
|
129
|
-
setValues (Session session) =
|
|
216
|
+
encodeNonExpiringPairs : Session -> Json.Encode.Value
|
|
217
|
+
encodeNonExpiringPairs (Session session) =
|
|
130
218
|
session
|
|
131
219
|
|> Dict.toList
|
|
132
220
|
|> List.filterMap
|
|
@@ -151,71 +239,56 @@ flashPrefix =
|
|
|
151
239
|
"__flash__"
|
|
152
240
|
|
|
153
241
|
|
|
154
|
-
{-| -}
|
|
155
|
-
clearFlashCookies : Dict String String -> Dict String String
|
|
156
|
-
clearFlashCookies dict =
|
|
157
|
-
Dict.Extra.removeWhen
|
|
158
|
-
(\key _ ->
|
|
159
|
-
key |> String.startsWith flashPrefix
|
|
160
|
-
)
|
|
161
|
-
dict
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
{-| -}
|
|
165
|
-
expectSession :
|
|
166
|
-
{ name : String
|
|
167
|
-
, secrets : DataSource (List String)
|
|
168
|
-
, sameSite : String
|
|
169
|
-
}
|
|
170
|
-
-> Parser request
|
|
171
|
-
-> (request -> Result () Session -> DataSource ( Session, Response data errorPage ))
|
|
172
|
-
-> Parser (DataSource (Response data errorPage))
|
|
173
|
-
expectSession config userRequest toRequest =
|
|
174
|
-
Request.map2
|
|
175
|
-
(\sessionCookie userRequestData ->
|
|
176
|
-
sessionCookie
|
|
177
|
-
|> decryptCookie config
|
|
178
|
-
|> DataSource.andThen
|
|
179
|
-
(encodeSessionUpdate config toRequest userRequestData)
|
|
180
|
-
)
|
|
181
|
-
(Request.expectCookie config.name)
|
|
182
|
-
userRequest
|
|
183
|
-
|
|
184
|
-
|
|
185
242
|
{-| -}
|
|
186
243
|
withSession :
|
|
187
244
|
{ name : String
|
|
188
245
|
, secrets : DataSource (List String)
|
|
189
|
-
,
|
|
246
|
+
, options : SetCookie.Options
|
|
190
247
|
}
|
|
191
|
-
->
|
|
192
|
-
->
|
|
193
|
-
-> Parser (DataSource (Response data errorPage))
|
|
194
|
-
withSession config userRequest
|
|
195
|
-
Request.map2
|
|
248
|
+
-> (request -> Result NotLoadedReason Session -> DataSource ( Session, Response data errorPage ))
|
|
249
|
+
-> Server.Request.Parser request
|
|
250
|
+
-> Server.Request.Parser (DataSource (Response data errorPage))
|
|
251
|
+
withSession config toRequest userRequest =
|
|
252
|
+
Server.Request.map2
|
|
196
253
|
(\maybeSessionCookie userRequestData ->
|
|
197
254
|
let
|
|
198
|
-
|
|
199
|
-
|
|
255
|
+
unsigned : DataSource (Result NotLoadedReason Session)
|
|
256
|
+
unsigned =
|
|
200
257
|
case maybeSessionCookie of
|
|
201
258
|
Just sessionCookie ->
|
|
202
259
|
sessionCookie
|
|
203
|
-
|>
|
|
204
|
-
|> DataSource.map
|
|
260
|
+
|> unsignCookie config
|
|
261
|
+
|> DataSource.map
|
|
262
|
+
(\unsignResult ->
|
|
263
|
+
case unsignResult of
|
|
264
|
+
Ok decoded ->
|
|
265
|
+
Ok decoded
|
|
266
|
+
|
|
267
|
+
Err () ->
|
|
268
|
+
Err InvalidSessionCookie
|
|
269
|
+
)
|
|
205
270
|
|
|
206
271
|
Nothing ->
|
|
207
|
-
|
|
272
|
+
Err NoSessionCookie
|
|
208
273
|
|> DataSource.succeed
|
|
209
274
|
in
|
|
210
|
-
|
|
275
|
+
unsigned
|
|
211
276
|
|> DataSource.andThen
|
|
212
277
|
(encodeSessionUpdate config toRequest userRequestData)
|
|
213
278
|
)
|
|
214
|
-
(Request.cookie config.name)
|
|
279
|
+
(Server.Request.cookie config.name)
|
|
215
280
|
userRequest
|
|
216
281
|
|
|
217
282
|
|
|
218
|
-
encodeSessionUpdate :
|
|
283
|
+
encodeSessionUpdate :
|
|
284
|
+
{ name : String
|
|
285
|
+
, secrets : DataSource (List String)
|
|
286
|
+
, options : SetCookie.Options
|
|
287
|
+
}
|
|
288
|
+
-> (c -> d -> DataSource ( Session, Response data errorPage ))
|
|
289
|
+
-> c
|
|
290
|
+
-> d
|
|
291
|
+
-> DataSource (Response data errorPage)
|
|
219
292
|
encodeSessionUpdate config toRequest userRequestData sessionResult =
|
|
220
293
|
sessionResult
|
|
221
294
|
|> toRequest userRequestData
|
|
@@ -225,25 +298,18 @@ encodeSessionUpdate config toRequest userRequestData sessionResult =
|
|
|
225
298
|
(\encoded ->
|
|
226
299
|
response
|
|
227
300
|
|> Server.Response.withSetCookieHeader
|
|
228
|
-
(SetCookie.setCookie config.name encoded
|
|
229
|
-
|> SetCookie.httpOnly
|
|
230
|
-
|> SetCookie.withPath "/"
|
|
231
|
-
-- TODO set expiration time
|
|
232
|
-
-- TODO do I need to encrypt the session expiration as part of it
|
|
233
|
-
-- TODO should I update the expiration time every time?
|
|
234
|
-
--|> SetCookie.withExpiration (Time.millisToPosix 100000000000)
|
|
235
|
-
)
|
|
301
|
+
(SetCookie.setCookie config.name encoded config.options)
|
|
236
302
|
)
|
|
237
|
-
(
|
|
238
|
-
(
|
|
303
|
+
(sign config.secrets
|
|
304
|
+
(encodeNonExpiringPairs sessionUpdate)
|
|
239
305
|
)
|
|
240
306
|
)
|
|
241
307
|
|
|
242
308
|
|
|
243
|
-
|
|
244
|
-
|
|
309
|
+
unsignCookie : { a | secrets : DataSource (List String) } -> String -> DataSource (Result () Session)
|
|
310
|
+
unsignCookie config sessionCookie =
|
|
245
311
|
sessionCookie
|
|
246
|
-
|>
|
|
312
|
+
|> unsign config.secrets (Json.Decode.dict Json.Decode.string)
|
|
247
313
|
|> DataSource.map
|
|
248
314
|
(Result.map
|
|
249
315
|
(\dict ->
|
|
@@ -265,8 +331,8 @@ decryptCookie config sessionCookie =
|
|
|
265
331
|
)
|
|
266
332
|
|
|
267
333
|
|
|
268
|
-
|
|
269
|
-
|
|
334
|
+
sign : DataSource (List String) -> Json.Encode.Value -> DataSource String
|
|
335
|
+
sign getSecrets input =
|
|
270
336
|
getSecrets
|
|
271
337
|
|> DataSource.andThen
|
|
272
338
|
(\secrets ->
|
|
@@ -293,8 +359,8 @@ encrypt getSecrets input =
|
|
|
293
359
|
)
|
|
294
360
|
|
|
295
361
|
|
|
296
|
-
|
|
297
|
-
|
|
362
|
+
unsign : DataSource (List String) -> Json.Decode.Decoder a -> String -> DataSource (Result () a)
|
|
363
|
+
unsign getSecrets decoder input =
|
|
298
364
|
getSecrets
|
|
299
365
|
|> DataSource.andThen
|
|
300
366
|
(\secrets ->
|
package/src/Server/SetCookie.elm
CHANGED
|
@@ -1,15 +1,38 @@
|
|
|
1
1
|
module Server.SetCookie exposing
|
|
2
|
-
( SetCookie
|
|
3
|
-
,
|
|
2
|
+
( SetCookie
|
|
3
|
+
, SameSite(..)
|
|
4
|
+
, Options, initOptions
|
|
5
|
+
, withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
|
|
4
6
|
, toString
|
|
5
7
|
)
|
|
6
8
|
|
|
7
|
-
{-|
|
|
9
|
+
{-| Server-rendered pages in your `elm-pages` can set cookies. `elm-pages` provides two high-level ways to work with cookies:
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
- [`Server.Session.withSession`](Server-Session#withSession)
|
|
12
|
+
- [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader)
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
[`Server.Session.withSession`](Server-Session#withSession) provides a high-level way to manage key-value pairs of data using cookie storage,
|
|
15
|
+
whereas `Server.Response.withSetCookieHeader` gives a more low-level tool for setting cookies. It's often best to use the
|
|
16
|
+
most high-level tool that will fit your use case.
|
|
17
|
+
|
|
18
|
+
You can learn more about the basics of cookies in the Web Platform in these helpful MDN documentation pages:
|
|
19
|
+
|
|
20
|
+
- <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
|
|
21
|
+
- <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>
|
|
22
|
+
|
|
23
|
+
@docs SetCookie
|
|
24
|
+
|
|
25
|
+
@docs SameSite
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## Options
|
|
29
|
+
|
|
30
|
+
@docs Options, initOptions
|
|
31
|
+
|
|
32
|
+
@docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Internal
|
|
13
36
|
|
|
14
37
|
@docs toString
|
|
15
38
|
|
|
@@ -24,8 +47,14 @@ import Utc
|
|
|
24
47
|
type alias SetCookie =
|
|
25
48
|
{ name : String
|
|
26
49
|
, value : String
|
|
27
|
-
,
|
|
28
|
-
|
|
50
|
+
, options : Options
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
{-| -}
|
|
55
|
+
type alias Options =
|
|
56
|
+
{ expiration : Maybe Time.Posix
|
|
57
|
+
, visibleToJavaScript : Bool
|
|
29
58
|
, maxAge : Maybe Int
|
|
30
59
|
, path : Maybe String
|
|
31
60
|
, domain : Maybe String
|
|
@@ -41,7 +70,11 @@ type SameSite
|
|
|
41
70
|
| None
|
|
42
71
|
|
|
43
72
|
|
|
44
|
-
{-| -
|
|
73
|
+
{-| Usually you'll want to use [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader) instead.
|
|
74
|
+
|
|
75
|
+
This is a low-level helper that's there in case you want it but most users will never need this.
|
|
76
|
+
|
|
77
|
+
-}
|
|
45
78
|
toString : SetCookie -> String
|
|
46
79
|
toString builder =
|
|
47
80
|
let
|
|
@@ -61,17 +94,25 @@ toString builder =
|
|
|
61
94
|
|
|
62
95
|
else
|
|
63
96
|
""
|
|
97
|
+
|
|
98
|
+
options : Options
|
|
99
|
+
options =
|
|
100
|
+
builder.options
|
|
101
|
+
|
|
102
|
+
httpOnly : Bool
|
|
103
|
+
httpOnly =
|
|
104
|
+
not options.visibleToJavaScript
|
|
64
105
|
in
|
|
65
106
|
builder.name
|
|
66
107
|
++ "="
|
|
67
108
|
++ Url.percentEncode builder.value
|
|
68
|
-
++ option "Expires" (
|
|
69
|
-
++ option "Max-Age" (
|
|
70
|
-
++ option "Path"
|
|
71
|
-
++ option "Domain"
|
|
72
|
-
++ option "SameSite" (
|
|
73
|
-
++ boolOption "HttpOnly"
|
|
74
|
-
++ boolOption "Secure"
|
|
109
|
+
++ option "Expires" (options.expiration |> Maybe.map Utc.fromTime)
|
|
110
|
+
++ option "Max-Age" (options.maxAge |> Maybe.map String.fromInt)
|
|
111
|
+
++ option "Path" options.path
|
|
112
|
+
++ option "Domain" options.domain
|
|
113
|
+
++ option "SameSite" (options.sameSite |> Maybe.map sameSiteToString)
|
|
114
|
+
++ boolOption "HttpOnly" httpOnly
|
|
115
|
+
++ boolOption "Secure" options.secure
|
|
75
116
|
|
|
76
117
|
|
|
77
118
|
sameSiteToString : SameSite -> String
|
|
@@ -88,12 +129,19 @@ sameSiteToString sameSite =
|
|
|
88
129
|
|
|
89
130
|
|
|
90
131
|
{-| -}
|
|
91
|
-
setCookie : String -> String -> SetCookie
|
|
92
|
-
setCookie name value =
|
|
132
|
+
setCookie : String -> String -> Options -> SetCookie
|
|
133
|
+
setCookie name value options =
|
|
93
134
|
{ name = name
|
|
94
135
|
, value = value
|
|
95
|
-
,
|
|
96
|
-
|
|
136
|
+
, options = options
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
{-| -}
|
|
141
|
+
initOptions : Options
|
|
142
|
+
initOptions =
|
|
143
|
+
{ expiration = Nothing
|
|
144
|
+
, visibleToJavaScript = False
|
|
97
145
|
, maxAge = Nothing
|
|
98
146
|
, path = Nothing
|
|
99
147
|
, domain = Nothing
|
|
@@ -103,7 +151,7 @@ setCookie name value =
|
|
|
103
151
|
|
|
104
152
|
|
|
105
153
|
{-| -}
|
|
106
|
-
withExpiration : Time.Posix ->
|
|
154
|
+
withExpiration : Time.Posix -> Options -> Options
|
|
107
155
|
withExpiration time builder =
|
|
108
156
|
{ builder
|
|
109
157
|
| expiration = Just time
|
|
@@ -111,23 +159,33 @@ withExpiration time builder =
|
|
|
111
159
|
|
|
112
160
|
|
|
113
161
|
{-| -}
|
|
114
|
-
withImmediateExpiration :
|
|
162
|
+
withImmediateExpiration : Options -> Options
|
|
115
163
|
withImmediateExpiration builder =
|
|
116
164
|
{ builder
|
|
117
165
|
| expiration = Just (Time.millisToPosix 0)
|
|
118
166
|
}
|
|
119
167
|
|
|
120
168
|
|
|
121
|
-
{-| -
|
|
122
|
-
|
|
123
|
-
|
|
169
|
+
{-| The default option in this API is for HttpOnly cookies <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#httponly>.
|
|
170
|
+
|
|
171
|
+
Cookies can be exposed so you can read them from JavaScript using `Document.cookie`. When this is intended and understood
|
|
172
|
+
then there's nothing unsafe about that (for example, if you are setting a `darkMode` cookie and what to access that
|
|
173
|
+
dynamically). In this API you opt into exposing a cookie you set to JavaScript to ensure cookies aren't exposed to JS unintentionally.
|
|
174
|
+
|
|
175
|
+
In general if you can accomplish your goal using HttpOnly cookies (i.e. not using `makeVisibleToJavaScript`) then
|
|
176
|
+
it's a good practice. With server-rendered `elm-pages` applications you can often manage your session state by pulling
|
|
177
|
+
in session data from cookies in a `DataSource` (which is resolved server-side before it ever reaches the browser).
|
|
178
|
+
|
|
179
|
+
-}
|
|
180
|
+
makeVisibleToJavaScript : Options -> Options
|
|
181
|
+
makeVisibleToJavaScript builder =
|
|
124
182
|
{ builder
|
|
125
|
-
|
|
|
183
|
+
| visibleToJavaScript = True
|
|
126
184
|
}
|
|
127
185
|
|
|
128
186
|
|
|
129
187
|
{-| -}
|
|
130
|
-
withMaxAge : Int ->
|
|
188
|
+
withMaxAge : Int -> Options -> Options
|
|
131
189
|
withMaxAge maxAge builder =
|
|
132
190
|
{ builder
|
|
133
191
|
| maxAge = Just maxAge
|
|
@@ -135,7 +193,7 @@ withMaxAge maxAge builder =
|
|
|
135
193
|
|
|
136
194
|
|
|
137
195
|
{-| -}
|
|
138
|
-
withPath : String ->
|
|
196
|
+
withPath : String -> Options -> Options
|
|
139
197
|
withPath path builder =
|
|
140
198
|
{ builder
|
|
141
199
|
| path = Just path
|
|
@@ -143,7 +201,7 @@ withPath path builder =
|
|
|
143
201
|
|
|
144
202
|
|
|
145
203
|
{-| -}
|
|
146
|
-
withDomain : String ->
|
|
204
|
+
withDomain : String -> Options -> Options
|
|
147
205
|
withDomain domain builder =
|
|
148
206
|
{ builder
|
|
149
207
|
| domain = Just domain
|
|
@@ -153,7 +211,7 @@ withDomain domain builder =
|
|
|
153
211
|
{-| Secure (only sent over https, or localhost on http) is the default. This overrides that and
|
|
154
212
|
removes the `Secure` attribute from the cookie.
|
|
155
213
|
-}
|
|
156
|
-
nonSecure :
|
|
214
|
+
nonSecure : Options -> Options
|
|
157
215
|
nonSecure builder =
|
|
158
216
|
{ builder
|
|
159
217
|
| secure = False
|
|
@@ -162,7 +220,7 @@ nonSecure builder =
|
|
|
162
220
|
|
|
163
221
|
{-| The default SameSite policy is Lax if one is not explicitly set. See the SameSite section in <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes>.
|
|
164
222
|
-}
|
|
165
|
-
withSameSite : SameSite ->
|
|
223
|
+
withSameSite : SameSite -> Options -> Options
|
|
166
224
|
withSameSite sameSite builder =
|
|
167
225
|
{ builder
|
|
168
226
|
| sameSite = Just sameSite
|