elm-pages 3.0.0-beta.4 → 3.0.0-beta.41
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/README.md +10 -1
- package/adapter/netlify.js +207 -0
- package/codegen/{elm-pages-codegen.js → elm-pages-codegen.cjs} +2828 -2933
- 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/elm-stuff/0.19.1/i.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1447 -342
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +17004 -13817
- 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 +4 -4
- package/generator/dead-code-review/elm.json +9 -7
- package/generator/dead-code-review/src/Pages/Review/DeadCodeEliminateData.elm +59 -10
- package/generator/dead-code-review/tests/Pages/Review/DeadCodeEliminateDataTest.elm +52 -36
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmo +0 -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/elm-stuff/0.19.1/i.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1447 -342
- package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +25025 -21739
- 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 +4 -4
- package/generator/review/elm.json +10 -10
- package/generator/src/RouteBuilder.elm +121 -114
- package/generator/src/SharedTemplate.elm +8 -7
- package/generator/src/SiteConfig.elm +3 -2
- package/generator/src/basepath-middleware.js +3 -3
- package/generator/src/build.js +209 -92
- package/generator/src/cli.js +292 -88
- package/generator/src/codegen.js +29 -27
- package/generator/src/compatibility-key.js +3 -0
- package/generator/src/compile-elm.js +43 -26
- package/generator/src/config.js +39 -0
- package/generator/src/copy-dir.js +2 -2
- package/generator/src/dev-server.js +176 -138
- package/generator/src/dir-helpers.js +9 -26
- package/generator/src/elm-codegen.js +5 -4
- package/generator/src/elm-file-constants.js +2 -3
- package/generator/src/error-formatter.js +12 -11
- package/generator/src/file-helpers.js +3 -4
- package/generator/src/generate-template-module-connector.js +23 -23
- package/generator/src/init.js +9 -8
- package/generator/src/pre-render-html.js +39 -28
- package/generator/src/render-test.js +109 -0
- package/generator/src/render-worker.js +25 -28
- package/generator/src/render.js +321 -142
- package/generator/src/request-cache.js +265 -162
- package/generator/src/resolve-elm-module.js +64 -0
- package/generator/src/rewrite-client-elm-json.js +6 -5
- package/generator/src/rewrite-elm-json-help.js +56 -0
- package/generator/src/rewrite-elm-json.js +17 -7
- package/generator/src/route-codegen-helpers.js +16 -31
- package/generator/src/seo-renderer.js +12 -7
- package/generator/src/vite-utils.js +77 -0
- package/generator/static-code/elm-pages.js +10 -0
- package/generator/static-code/hmr.js +79 -13
- package/generator/template/app/Api.elm +6 -5
- package/generator/template/app/Effect.elm +123 -0
- package/generator/template/app/ErrorPage.elm +37 -6
- package/generator/template/app/Route/Index.elm +17 -10
- package/generator/template/app/Shared.elm +24 -47
- package/generator/template/app/Site.elm +19 -6
- package/generator/template/app/View.elm +1 -8
- package/generator/template/elm-tooling.json +0 -3
- package/generator/template/elm.json +32 -24
- package/generator/template/package.json +10 -4
- package/package.json +30 -27
- package/src/ApiRoute.elm +199 -61
- package/src/BackendTask/Custom.elm +325 -0
- package/src/BackendTask/Env.elm +90 -0
- package/src/{DataSource → BackendTask}/File.elm +171 -56
- package/src/{DataSource → BackendTask}/Glob.elm +136 -125
- package/src/BackendTask/Http.elm +679 -0
- package/src/{DataSource → BackendTask}/Internal/Glob.elm +1 -1
- package/src/BackendTask/Internal/Request.elm +69 -0
- package/src/BackendTask/Random.elm +79 -0
- package/src/BackendTask/Time.elm +47 -0
- package/src/BackendTask.elm +531 -0
- package/src/FatalError.elm +90 -0
- package/src/Head/Seo.elm +4 -4
- package/src/Head.elm +237 -7
- package/src/HtmlPrinter.elm +7 -3
- package/src/Internal/ApiRoute.elm +7 -5
- package/src/PageServerResponse.elm +6 -1
- package/src/Pages/ConcurrentSubmission.elm +127 -0
- package/src/Pages/Form.elm +340 -0
- package/src/Pages/FormData.elm +18 -0
- package/src/Pages/GeneratorProgramConfig.elm +15 -0
- package/src/Pages/Internal/FatalError.elm +5 -0
- package/src/Pages/Internal/Msg.elm +93 -0
- package/src/Pages/Internal/NotFoundReason.elm +4 -4
- package/src/Pages/Internal/Platform/Cli.elm +617 -768
- package/src/Pages/Internal/Platform/CompatibilityKey.elm +6 -0
- package/src/Pages/Internal/Platform/Effect.elm +1 -2
- package/src/Pages/Internal/Platform/GeneratorApplication.elm +379 -0
- package/src/Pages/Internal/Platform/StaticResponses.elm +65 -276
- package/src/Pages/Internal/Platform/ToJsPayload.elm +6 -9
- package/src/Pages/Internal/Platform.elm +359 -225
- package/src/Pages/Internal/ResponseSketch.elm +2 -2
- package/src/Pages/Internal/Script.elm +17 -0
- package/src/Pages/Internal/StaticHttpBody.elm +35 -1
- package/src/Pages/Manifest.elm +52 -11
- package/src/Pages/Navigation.elm +87 -0
- package/src/Pages/PageUrl.elm +26 -12
- package/src/Pages/ProgramConfig.elm +35 -23
- package/src/Pages/Script.elm +166 -0
- package/src/Pages/SiteConfig.elm +3 -2
- package/src/Pages/StaticHttp/Request.elm +2 -2
- package/src/Pages/StaticHttpRequest.elm +23 -99
- package/src/Pages/Url.elm +3 -3
- package/src/PagesMsg.elm +88 -0
- package/src/QueryParams.elm +21 -172
- package/src/RenderRequest.elm +7 -7
- package/src/RequestsAndPending.elm +37 -20
- package/src/Result/Extra.elm +26 -0
- package/src/Scaffold/Form.elm +569 -0
- package/src/Scaffold/Route.elm +1411 -0
- package/src/Server/Request.elm +74 -72
- package/src/Server/Session.elm +62 -42
- package/src/Server/SetCookie.elm +80 -32
- package/src/Stub.elm +53 -0
- package/src/Test/Html/Internal/ElmHtml/ToString.elm +8 -9
- package/src/{Path.elm → UrlPath.elm} +33 -36
- package/src/DataSource/Env.elm +0 -38
- package/src/DataSource/Http.elm +0 -446
- package/src/DataSource/Internal/Request.elm +0 -20
- package/src/DataSource/Port.elm +0 -90
- package/src/DataSource.elm +0 -538
- package/src/Form/Field.elm +0 -717
- package/src/Form/FieldStatus.elm +0 -36
- package/src/Form/FieldView.elm +0 -417
- package/src/Form/FormData.elm +0 -22
- package/src/Form/Validation.elm +0 -391
- package/src/Form/Value.elm +0 -118
- package/src/Form.elm +0 -1683
- package/src/FormDecoder.elm +0 -102
- package/src/Pages/FormState.elm +0 -256
- package/src/Pages/Generate.elm +0 -800
- package/src/Pages/Internal/Form.elm +0 -17
- package/src/Pages/Msg.elm +0 -79
- package/src/Pages/Transition.elm +0 -70
package/src/Server/Request.elm
CHANGED
|
@@ -86,17 +86,19 @@ module Server.Request exposing
|
|
|
86
86
|
|
|
87
87
|
-}
|
|
88
88
|
|
|
89
|
+
import BackendTask exposing (BackendTask)
|
|
89
90
|
import CookieParser
|
|
90
|
-
import DataSource exposing (DataSource)
|
|
91
91
|
import Dict exposing (Dict)
|
|
92
|
+
import FatalError exposing (FatalError)
|
|
92
93
|
import Form
|
|
93
|
-
import Form.
|
|
94
|
+
import Form.Handler
|
|
95
|
+
import Form.Validation as Validation
|
|
94
96
|
import FormData
|
|
95
97
|
import Internal.Request
|
|
96
98
|
import Json.Decode
|
|
97
99
|
import Json.Encode
|
|
98
100
|
import List.NonEmpty
|
|
99
|
-
import Pages.
|
|
101
|
+
import Pages.Form
|
|
100
102
|
import QueryParams
|
|
101
103
|
import Time
|
|
102
104
|
import Url
|
|
@@ -118,17 +120,17 @@ Note that this data is not available for pre-rendered pages or pre-rendered API
|
|
|
118
120
|
This is because when a page is pre-rendered, there _is_ no incoming HTTP request to respond to, it is rendered before a user
|
|
119
121
|
requests the page and then the pre-rendered page is served as a plain file (without running your Route Module).
|
|
120
122
|
|
|
121
|
-
That's why `RouteBuilder.preRender` has `data : RouteParams ->
|
|
123
|
+
That's why `RouteBuilder.preRender` has `data : RouteParams -> BackendTask Data`:
|
|
122
124
|
|
|
123
|
-
import
|
|
125
|
+
import BackendTask exposing (BackendTask)
|
|
124
126
|
import RouteBuilder exposing (StatelessRoute)
|
|
125
127
|
|
|
126
128
|
type alias Data =
|
|
127
129
|
{}
|
|
128
130
|
|
|
129
|
-
data : RouteParams ->
|
|
131
|
+
data : RouteParams -> BackendTask Data
|
|
130
132
|
data routeParams =
|
|
131
|
-
|
|
133
|
+
BackendTask.succeed Data
|
|
132
134
|
|
|
133
135
|
route : StatelessRoute RouteParams Data ActionData
|
|
134
136
|
route =
|
|
@@ -141,7 +143,7 @@ That's why `RouteBuilder.preRender` has `data : RouteParams -> DataSource Data`:
|
|
|
141
143
|
|
|
142
144
|
A server-rendered Route Module _does_ have access to a user's incoming HTTP request because it runs every time the page
|
|
143
145
|
is loaded. That's why `data` is a `Request.Parser` in server-rendered Route Modules. Since you have an incoming HTTP request for server-rendered routes,
|
|
144
|
-
`RouteBuilder.serverRender` has `data : RouteParams -> Request.Parser (
|
|
146
|
+
`RouteBuilder.serverRender` has `data : RouteParams -> Request.Parser (BackendTask (Response Data))`. That means that you
|
|
145
147
|
can use the incoming HTTP request data to choose how to respond. For example, you could check for a dark-mode preference
|
|
146
148
|
cookie and render a light- or dark-themed page and render a different page.
|
|
147
149
|
|
|
@@ -151,7 +153,7 @@ That's a mouthful, so let's unpack what it means.
|
|
|
151
153
|
|
|
152
154
|
data from the request payload using a Server Request Parser.
|
|
153
155
|
|
|
154
|
-
import
|
|
156
|
+
import BackendTask exposing (BackendTask)
|
|
155
157
|
import RouteBuilder exposing (StatelessRoute)
|
|
156
158
|
import Server.Request as Request exposing (Request)
|
|
157
159
|
import Server.Response as Response exposing (Response)
|
|
@@ -161,11 +163,11 @@ data from the request payload using a Server Request Parser.
|
|
|
161
163
|
|
|
162
164
|
data :
|
|
163
165
|
RouteParams
|
|
164
|
-
-> Request.Parser (
|
|
166
|
+
-> Request.Parser (BackendTask (Response Data))
|
|
165
167
|
data routeParams =
|
|
166
168
|
{}
|
|
167
169
|
|> Server.Response.render
|
|
168
|
-
|>
|
|
170
|
+
|> BackendTask.succeed
|
|
169
171
|
|> Request.succeed
|
|
170
172
|
|
|
171
173
|
route : StatelessRoute RouteParams Data ActionData
|
|
@@ -225,7 +227,7 @@ succeed value =
|
|
|
225
227
|
|
|
226
228
|
{-| TODO internal only
|
|
227
229
|
-}
|
|
228
|
-
getDecoder : Parser (
|
|
230
|
+
getDecoder : Parser (BackendTask error response) -> Json.Decode.Decoder (Result ( ValidationError, List ValidationError ) (BackendTask error response))
|
|
229
231
|
getDecoder (Internal.Request.Parser decoder) =
|
|
230
232
|
decoder
|
|
231
233
|
|> Json.Decode.map
|
|
@@ -650,7 +652,6 @@ expectQueryParam name =
|
|
|
650
652
|
maybeParamValue =
|
|
651
653
|
queryString
|
|
652
654
|
|> QueryParams.fromString
|
|
653
|
-
|> QueryParams.toDict
|
|
654
655
|
|> Dict.get name
|
|
655
656
|
|> Maybe.andThen List.head
|
|
656
657
|
in
|
|
@@ -689,7 +690,6 @@ findFirstQueryParam : String -> String -> Maybe String
|
|
|
689
690
|
findFirstQueryParam name queryString =
|
|
690
691
|
queryString
|
|
691
692
|
|> QueryParams.fromString
|
|
692
|
-
|> QueryParams.toDict
|
|
693
693
|
|> Dict.get name
|
|
694
694
|
|> Maybe.andThen List.head
|
|
695
695
|
|
|
@@ -704,7 +704,6 @@ queryParams =
|
|
|
704
704
|
|> Url.fromString
|
|
705
705
|
|> Maybe.andThen .query
|
|
706
706
|
|> Maybe.map QueryParams.fromString
|
|
707
|
-
|> Maybe.map QueryParams.toDict
|
|
708
707
|
|> Maybe.withDefault Dict.empty
|
|
709
708
|
)
|
|
710
709
|
|
|
@@ -879,94 +878,97 @@ fileField_ name =
|
|
|
879
878
|
|> Internal.Request.Parser
|
|
880
879
|
|
|
881
880
|
|
|
881
|
+
runForm : Validation.Validation error parsed kind constraints -> Form.Validated error parsed
|
|
882
|
+
runForm validation =
|
|
883
|
+
Form.Handler.run []
|
|
884
|
+
(Form.Handler.init identity
|
|
885
|
+
(Form.form
|
|
886
|
+
{ combine = validation
|
|
887
|
+
, view = []
|
|
888
|
+
}
|
|
889
|
+
)
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
|
|
882
893
|
{-| -}
|
|
883
894
|
formDataWithServerValidation :
|
|
884
|
-
Form.
|
|
885
|
-
-> Parser (
|
|
895
|
+
Pages.Form.Handler error combined
|
|
896
|
+
-> Parser (BackendTask FatalError (Result (Form.ServerResponse error) ( Form.ServerResponse error, combined )))
|
|
886
897
|
formDataWithServerValidation formParsers =
|
|
887
898
|
rawFormData
|
|
888
899
|
|> andThen
|
|
889
900
|
(\rawFormData_ ->
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
Form.runOneOfServerSide
|
|
893
|
-
rawFormData_
|
|
894
|
-
formParsers
|
|
895
|
-
in
|
|
896
|
-
case ( maybeDecoded, errors |> Dict.toList |> List.filter (\( _, value ) -> value |> List.isEmpty |> not) |> List.NonEmpty.fromList ) of
|
|
897
|
-
( Just decoded, Nothing ) ->
|
|
901
|
+
case Form.Handler.run rawFormData_ formParsers of
|
|
902
|
+
Form.Valid decoded ->
|
|
898
903
|
succeed
|
|
899
904
|
(decoded
|
|
900
|
-
|>
|
|
901
|
-
(\
|
|
902
|
-
case
|
|
903
|
-
|
|
905
|
+
|> BackendTask.map
|
|
906
|
+
(\clientValidated ->
|
|
907
|
+
case runForm clientValidated of
|
|
908
|
+
Form.Valid decodedFinal ->
|
|
904
909
|
Ok
|
|
905
|
-
(
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
910
|
+
( { persisted =
|
|
911
|
+
{ fields = Just rawFormData_
|
|
912
|
+
, clientSideErrors = Nothing
|
|
913
|
+
}
|
|
914
|
+
, serverSideErrors = Dict.empty
|
|
915
|
+
}
|
|
909
916
|
, decodedFinal
|
|
910
917
|
)
|
|
911
918
|
|
|
912
|
-
|
|
919
|
+
Form.Invalid _ errors2 ->
|
|
913
920
|
Err
|
|
914
|
-
|
|
915
|
-
{ fields = rawFormData_
|
|
916
|
-
,
|
|
917
|
-
maybeErrors
|
|
918
|
-
|> Maybe.map List.NonEmpty.toList
|
|
919
|
-
|> Maybe.withDefault []
|
|
920
|
-
|> Dict.fromList
|
|
921
|
+
{ persisted =
|
|
922
|
+
{ fields = Just rawFormData_
|
|
923
|
+
, clientSideErrors = Just errors2
|
|
921
924
|
}
|
|
922
|
-
|
|
925
|
+
, serverSideErrors = Dict.empty
|
|
926
|
+
}
|
|
923
927
|
)
|
|
924
928
|
)
|
|
925
929
|
|
|
926
|
-
|
|
930
|
+
Form.Invalid _ errors ->
|
|
927
931
|
Err
|
|
928
|
-
|
|
929
|
-
{ fields = rawFormData_
|
|
930
|
-
,
|
|
931
|
-
maybeErrors
|
|
932
|
-
|> Maybe.map List.NonEmpty.toList
|
|
933
|
-
|> Maybe.withDefault []
|
|
934
|
-
|> Dict.fromList
|
|
932
|
+
{ persisted =
|
|
933
|
+
{ fields = Just rawFormData_
|
|
934
|
+
, clientSideErrors = Just errors
|
|
935
935
|
}
|
|
936
|
-
|
|
937
|
-
|
|
936
|
+
, serverSideErrors = Dict.empty
|
|
937
|
+
}
|
|
938
|
+
|> BackendTask.succeed
|
|
938
939
|
|> succeed
|
|
939
940
|
)
|
|
940
941
|
|
|
941
942
|
|
|
942
943
|
{-| -}
|
|
943
944
|
formData :
|
|
944
|
-
Form.
|
|
945
|
-
-> Parser (
|
|
945
|
+
Form.Handler.Handler error combined
|
|
946
|
+
-> Parser ( Form.ServerResponse error, Form.Validated error combined )
|
|
946
947
|
formData formParsers =
|
|
947
948
|
rawFormData
|
|
948
949
|
|> andThen
|
|
949
950
|
(\rawFormData_ ->
|
|
950
|
-
|
|
951
|
-
(
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
951
|
+
case Form.Handler.run rawFormData_ formParsers of
|
|
952
|
+
(Form.Valid _) as validated ->
|
|
953
|
+
( { persisted =
|
|
954
|
+
{ fields = Just rawFormData_
|
|
955
|
+
, clientSideErrors = Just Dict.empty
|
|
956
|
+
}
|
|
957
|
+
, serverSideErrors = Dict.empty
|
|
958
|
+
}
|
|
959
|
+
, validated
|
|
960
|
+
)
|
|
959
961
|
|> succeed
|
|
960
962
|
|
|
961
|
-
( _
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
963
|
+
(Form.Invalid _ maybeErrors) as validated ->
|
|
964
|
+
( { persisted =
|
|
965
|
+
{ fields = Just rawFormData_
|
|
966
|
+
, clientSideErrors = Just maybeErrors
|
|
967
|
+
}
|
|
968
|
+
, serverSideErrors = Dict.empty
|
|
969
|
+
}
|
|
970
|
+
, validated
|
|
971
|
+
)
|
|
970
972
|
|> succeed
|
|
971
973
|
)
|
|
972
974
|
|
package/src/Server/Session.elm
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module Server.Session exposing
|
|
2
|
-
( withSession
|
|
2
|
+
( withSession, withSessionResult
|
|
3
3
|
, NotLoadedReason(..)
|
|
4
4
|
, Session, empty, get, insert, remove, update, withFlash
|
|
5
5
|
)
|
|
@@ -13,13 +13,13 @@ to choose which requests to respond to and how to extract structured data from t
|
|
|
13
13
|
Using these functions, you can store and read session data in cookies to maintain state between requests.
|
|
14
14
|
For example, TODO:
|
|
15
15
|
|
|
16
|
-
action : RouteParams -> Request.Parser (
|
|
16
|
+
action : RouteParams -> Request.Parser (BackendTask (Response ActionData ErrorPage))
|
|
17
17
|
action routeParams =
|
|
18
18
|
MySession.withSession
|
|
19
19
|
(Request.formDataWithServerValidation (form |> Form.initCombinedServer identity))
|
|
20
20
|
(\nameResultData session ->
|
|
21
21
|
nameResultData
|
|
22
|
-
|>
|
|
22
|
+
|> BackendTask.map
|
|
23
23
|
(\nameResult ->
|
|
24
24
|
case nameResult of
|
|
25
25
|
Err errors ->
|
|
@@ -42,7 +42,7 @@ For example, TODO:
|
|
|
42
42
|
)
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
The elm-pages framework will manage signing these cookies using the `secrets :
|
|
45
|
+
The elm-pages framework will manage signing these cookies using the `secrets : BackendTask (List String)` you pass in.
|
|
46
46
|
That means that the values you set in your session will be directly visible to anyone who has access to the cookie
|
|
47
47
|
(so don't directly store sensitive data in your session). Since the session cookie is signed using the secret you provide,
|
|
48
48
|
the cookie will be invalidated if it is tampered with because it won't match when elm-pages verifies that it has been
|
|
@@ -51,15 +51,15 @@ signed with your secrets. Of course you need to provide secure secrets and treat
|
|
|
51
51
|
|
|
52
52
|
### Rotating Secrets
|
|
53
53
|
|
|
54
|
-
The first String in `secrets :
|
|
54
|
+
The first String in `secrets : BackendTask (List String)` will be used to sign sessions, while the remaining String's will
|
|
55
55
|
still be used to attempt to "unsign" the cookies. So if you have a single secret:
|
|
56
56
|
|
|
57
57
|
Session.withSession
|
|
58
58
|
{ name = "mysession"
|
|
59
59
|
, secrets =
|
|
60
|
-
|
|
60
|
+
BackendTask.map List.singleton
|
|
61
61
|
(Env.expect "SESSION_SECRET2022-09-01")
|
|
62
|
-
, options =
|
|
62
|
+
, options = Nothing
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
Then you add a second secret
|
|
@@ -67,11 +67,11 @@ Then you add a second secret
|
|
|
67
67
|
Session.withSession
|
|
68
68
|
{ name = "mysession"
|
|
69
69
|
, secrets =
|
|
70
|
-
|
|
70
|
+
BackendTask.map2
|
|
71
71
|
(\newSecret oldSecret -> [ newSecret, oldSecret ])
|
|
72
72
|
(Env.expect "SESSION_SECRET2022-12-01")
|
|
73
73
|
(Env.expect "SESSION_SECRET2022-09-01")
|
|
74
|
-
, options =
|
|
74
|
+
, options = Nothing
|
|
75
75
|
}
|
|
76
76
|
|
|
77
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
|
|
@@ -87,16 +87,16 @@ it will invalidate all cookies signed with that. For example, if we remove our o
|
|
|
87
87
|
Session.withSession
|
|
88
88
|
{ name = "mysession"
|
|
89
89
|
, secrets =
|
|
90
|
-
|
|
90
|
+
BackendTask.map List.singleton
|
|
91
91
|
(Env.expect "SESSION_SECRET2022-12-01")
|
|
92
|
-
, options =
|
|
92
|
+
, options = Nothing
|
|
93
93
|
}
|
|
94
94
|
|
|
95
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
96
|
(so `withSession` would parse the session for that request as `Nothing`). It's standard for cookies to have an expiration date,
|
|
97
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
98
|
|
|
99
|
-
@docs withSession
|
|
99
|
+
@docs withSession, withSessionResult
|
|
100
100
|
|
|
101
101
|
@docs NotLoadedReason
|
|
102
102
|
|
|
@@ -107,9 +107,9 @@ so there's nothing wrong with an old session expiring (and the browser will even
|
|
|
107
107
|
|
|
108
108
|
-}
|
|
109
109
|
|
|
110
|
-
import
|
|
111
|
-
import
|
|
112
|
-
import
|
|
110
|
+
import BackendTask exposing (BackendTask)
|
|
111
|
+
import BackendTask.Http
|
|
112
|
+
import BackendTask.Internal.Request
|
|
113
113
|
import Dict exposing (Dict)
|
|
114
114
|
import Json.Decode
|
|
115
115
|
import Json.Encode
|
|
@@ -242,23 +242,43 @@ flashPrefix =
|
|
|
242
242
|
{-| -}
|
|
243
243
|
withSession :
|
|
244
244
|
{ name : String
|
|
245
|
-
, secrets :
|
|
246
|
-
, options : SetCookie.Options
|
|
245
|
+
, secrets : BackendTask error (List String)
|
|
246
|
+
, options : Maybe SetCookie.Options
|
|
247
247
|
}
|
|
248
|
-
-> (request ->
|
|
248
|
+
-> (request -> Session -> BackendTask error ( Session, Response data errorPage ))
|
|
249
249
|
-> Server.Request.Parser request
|
|
250
|
-
-> Server.Request.Parser (
|
|
250
|
+
-> Server.Request.Parser (BackendTask error (Response data errorPage))
|
|
251
251
|
withSession config toRequest userRequest =
|
|
252
|
+
withSessionResult config
|
|
253
|
+
(\request session ->
|
|
254
|
+
toRequest request
|
|
255
|
+
(session
|
|
256
|
+
|> Result.withDefault empty
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
userRequest
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
{-| -}
|
|
263
|
+
withSessionResult :
|
|
264
|
+
{ name : String
|
|
265
|
+
, secrets : BackendTask error (List String)
|
|
266
|
+
, options : Maybe SetCookie.Options
|
|
267
|
+
}
|
|
268
|
+
-> (request -> Result NotLoadedReason Session -> BackendTask error ( Session, Response data errorPage ))
|
|
269
|
+
-> Server.Request.Parser request
|
|
270
|
+
-> Server.Request.Parser (BackendTask error (Response data errorPage))
|
|
271
|
+
withSessionResult config toRequest userRequest =
|
|
252
272
|
Server.Request.map2
|
|
253
273
|
(\maybeSessionCookie userRequestData ->
|
|
254
274
|
let
|
|
255
|
-
unsigned :
|
|
275
|
+
unsigned : BackendTask error (Result NotLoadedReason Session)
|
|
256
276
|
unsigned =
|
|
257
277
|
case maybeSessionCookie of
|
|
258
278
|
Just sessionCookie ->
|
|
259
279
|
sessionCookie
|
|
260
280
|
|> unsignCookie config
|
|
261
|
-
|>
|
|
281
|
+
|> BackendTask.map
|
|
262
282
|
(\unsignResult ->
|
|
263
283
|
case unsignResult of
|
|
264
284
|
Ok decoded ->
|
|
@@ -270,10 +290,10 @@ withSession config toRequest userRequest =
|
|
|
270
290
|
|
|
271
291
|
Nothing ->
|
|
272
292
|
Err NoSessionCookie
|
|
273
|
-
|>
|
|
293
|
+
|> BackendTask.succeed
|
|
274
294
|
in
|
|
275
295
|
unsigned
|
|
276
|
-
|>
|
|
296
|
+
|> BackendTask.andThen
|
|
277
297
|
(encodeSessionUpdate config toRequest userRequestData)
|
|
278
298
|
)
|
|
279
299
|
(Server.Request.cookie config.name)
|
|
@@ -282,23 +302,23 @@ withSession config toRequest userRequest =
|
|
|
282
302
|
|
|
283
303
|
encodeSessionUpdate :
|
|
284
304
|
{ name : String
|
|
285
|
-
, secrets :
|
|
286
|
-
, options : SetCookie.Options
|
|
305
|
+
, secrets : BackendTask error (List String)
|
|
306
|
+
, options : Maybe SetCookie.Options
|
|
287
307
|
}
|
|
288
|
-
-> (c -> d ->
|
|
308
|
+
-> (c -> d -> BackendTask error ( Session, Response data errorPage ))
|
|
289
309
|
-> c
|
|
290
310
|
-> d
|
|
291
|
-
->
|
|
311
|
+
-> BackendTask error (Response data errorPage)
|
|
292
312
|
encodeSessionUpdate config toRequest userRequestData sessionResult =
|
|
293
313
|
sessionResult
|
|
294
314
|
|> toRequest userRequestData
|
|
295
|
-
|>
|
|
315
|
+
|> BackendTask.andThen
|
|
296
316
|
(\( sessionUpdate, response ) ->
|
|
297
|
-
|
|
317
|
+
BackendTask.map
|
|
298
318
|
(\encoded ->
|
|
299
319
|
response
|
|
300
320
|
|> Server.Response.withSetCookieHeader
|
|
301
|
-
(SetCookie.setCookie config.name encoded config.options)
|
|
321
|
+
(SetCookie.setCookie config.name encoded (config.options |> Maybe.withDefault SetCookie.options))
|
|
302
322
|
)
|
|
303
323
|
(sign config.secrets
|
|
304
324
|
(encodeNonExpiringPairs sessionUpdate)
|
|
@@ -306,11 +326,11 @@ encodeSessionUpdate config toRequest userRequestData sessionResult =
|
|
|
306
326
|
)
|
|
307
327
|
|
|
308
328
|
|
|
309
|
-
unsignCookie : { a | secrets :
|
|
329
|
+
unsignCookie : { a | secrets : BackendTask error (List String) } -> String -> BackendTask error (Result () Session)
|
|
310
330
|
unsignCookie config sessionCookie =
|
|
311
331
|
sessionCookie
|
|
312
332
|
|> unsign config.secrets (Json.Decode.dict Json.Decode.string)
|
|
313
|
-
|>
|
|
333
|
+
|> BackendTask.map
|
|
314
334
|
(Result.map
|
|
315
335
|
(\dict ->
|
|
316
336
|
dict
|
|
@@ -331,15 +351,15 @@ unsignCookie config sessionCookie =
|
|
|
331
351
|
)
|
|
332
352
|
|
|
333
353
|
|
|
334
|
-
sign :
|
|
354
|
+
sign : BackendTask error (List String) -> Json.Encode.Value -> BackendTask error String
|
|
335
355
|
sign getSecrets input =
|
|
336
356
|
getSecrets
|
|
337
|
-
|>
|
|
357
|
+
|> BackendTask.andThen
|
|
338
358
|
(\secrets ->
|
|
339
|
-
|
|
359
|
+
BackendTask.Internal.Request.request
|
|
340
360
|
{ name = "encrypt"
|
|
341
361
|
, body =
|
|
342
|
-
|
|
362
|
+
BackendTask.Http.jsonBody
|
|
343
363
|
(Json.Encode.object
|
|
344
364
|
[ ( "values", input )
|
|
345
365
|
, ( "secret"
|
|
@@ -353,21 +373,21 @@ sign getSecrets input =
|
|
|
353
373
|
]
|
|
354
374
|
)
|
|
355
375
|
, expect =
|
|
356
|
-
|
|
376
|
+
BackendTask.Http.expectJson
|
|
357
377
|
Json.Decode.string
|
|
358
378
|
}
|
|
359
379
|
)
|
|
360
380
|
|
|
361
381
|
|
|
362
|
-
unsign :
|
|
382
|
+
unsign : BackendTask error (List String) -> Json.Decode.Decoder a -> String -> BackendTask error (Result () a)
|
|
363
383
|
unsign getSecrets decoder input =
|
|
364
384
|
getSecrets
|
|
365
|
-
|>
|
|
385
|
+
|> BackendTask.andThen
|
|
366
386
|
(\secrets ->
|
|
367
|
-
|
|
387
|
+
BackendTask.Internal.Request.request
|
|
368
388
|
{ name = "decrypt"
|
|
369
389
|
, body =
|
|
370
|
-
|
|
390
|
+
BackendTask.Http.jsonBody
|
|
371
391
|
(Json.Encode.object
|
|
372
392
|
[ ( "input", Json.Encode.string input )
|
|
373
393
|
, ( "secrets", Json.Encode.list Json.Encode.string secrets )
|
|
@@ -377,6 +397,6 @@ unsign getSecrets decoder input =
|
|
|
377
397
|
decoder
|
|
378
398
|
|> Json.Decode.nullable
|
|
379
399
|
|> Json.Decode.map (Result.fromMaybe ())
|
|
380
|
-
|>
|
|
400
|
+
|> BackendTask.Http.expectJson
|
|
381
401
|
}
|
|
382
402
|
)
|