elm-pages 3.0.0-beta.4 → 3.0.0-beta.40
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} +2678 -2725
- 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 +115 -109
- package/generator/src/SharedTemplate.elm +3 -2
- 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 +537 -0
- package/src/FatalError.elm +90 -0
- 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/Form.elm +229 -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/Platform/Cli.elm +612 -763
- 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 +327 -194
- package/src/Pages/Internal/Script.elm +17 -0
- package/src/Pages/Internal/StaticHttpBody.elm +35 -1
- package/src/Pages/Manifest.elm +29 -4
- package/src/Pages/PageUrl.elm +23 -9
- package/src/Pages/ProgramConfig.elm +26 -15
- package/src/Pages/Script.elm +109 -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/Transition.elm +12 -3
- package/src/PagesMsg.elm +82 -0
- package/src/Path.elm +16 -19
- package/src/QueryParams.elm +21 -172
- package/src/RequestsAndPending.elm +37 -20
- package/src/Result/Extra.elm +26 -0
- package/src/Scaffold/Form.elm +546 -0
- package/src/Scaffold/Route.elm +1402 -0
- package/src/Server/Request.elm +73 -72
- package/src/Server/Session.elm +62 -42
- package/src/Server/SetCookie.elm +12 -4
- package/src/Stub.elm +53 -0
- package/src/Test/Html/Internal/ElmHtml/ToString.elm +8 -9
- 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/Server/Request.elm
CHANGED
|
@@ -86,17 +86,18 @@ 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.Internal.Form exposing (Validation(..))
|
|
100
101
|
import QueryParams
|
|
101
102
|
import Time
|
|
102
103
|
import Url
|
|
@@ -118,17 +119,17 @@ Note that this data is not available for pre-rendered pages or pre-rendered API
|
|
|
118
119
|
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
120
|
requests the page and then the pre-rendered page is served as a plain file (without running your Route Module).
|
|
120
121
|
|
|
121
|
-
That's why `RouteBuilder.preRender` has `data : RouteParams ->
|
|
122
|
+
That's why `RouteBuilder.preRender` has `data : RouteParams -> BackendTask Data`:
|
|
122
123
|
|
|
123
|
-
import
|
|
124
|
+
import BackendTask exposing (BackendTask)
|
|
124
125
|
import RouteBuilder exposing (StatelessRoute)
|
|
125
126
|
|
|
126
127
|
type alias Data =
|
|
127
128
|
{}
|
|
128
129
|
|
|
129
|
-
data : RouteParams ->
|
|
130
|
+
data : RouteParams -> BackendTask Data
|
|
130
131
|
data routeParams =
|
|
131
|
-
|
|
132
|
+
BackendTask.succeed Data
|
|
132
133
|
|
|
133
134
|
route : StatelessRoute RouteParams Data ActionData
|
|
134
135
|
route =
|
|
@@ -141,7 +142,7 @@ That's why `RouteBuilder.preRender` has `data : RouteParams -> DataSource Data`:
|
|
|
141
142
|
|
|
142
143
|
A server-rendered Route Module _does_ have access to a user's incoming HTTP request because it runs every time the page
|
|
143
144
|
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 (
|
|
145
|
+
`RouteBuilder.serverRender` has `data : RouteParams -> Request.Parser (BackendTask (Response Data))`. That means that you
|
|
145
146
|
can use the incoming HTTP request data to choose how to respond. For example, you could check for a dark-mode preference
|
|
146
147
|
cookie and render a light- or dark-themed page and render a different page.
|
|
147
148
|
|
|
@@ -151,7 +152,7 @@ That's a mouthful, so let's unpack what it means.
|
|
|
151
152
|
|
|
152
153
|
data from the request payload using a Server Request Parser.
|
|
153
154
|
|
|
154
|
-
import
|
|
155
|
+
import BackendTask exposing (BackendTask)
|
|
155
156
|
import RouteBuilder exposing (StatelessRoute)
|
|
156
157
|
import Server.Request as Request exposing (Request)
|
|
157
158
|
import Server.Response as Response exposing (Response)
|
|
@@ -161,11 +162,11 @@ data from the request payload using a Server Request Parser.
|
|
|
161
162
|
|
|
162
163
|
data :
|
|
163
164
|
RouteParams
|
|
164
|
-
-> Request.Parser (
|
|
165
|
+
-> Request.Parser (BackendTask (Response Data))
|
|
165
166
|
data routeParams =
|
|
166
167
|
{}
|
|
167
168
|
|> Server.Response.render
|
|
168
|
-
|>
|
|
169
|
+
|> BackendTask.succeed
|
|
169
170
|
|> Request.succeed
|
|
170
171
|
|
|
171
172
|
route : StatelessRoute RouteParams Data ActionData
|
|
@@ -225,7 +226,7 @@ succeed value =
|
|
|
225
226
|
|
|
226
227
|
{-| TODO internal only
|
|
227
228
|
-}
|
|
228
|
-
getDecoder : Parser (
|
|
229
|
+
getDecoder : Parser (BackendTask error response) -> Json.Decode.Decoder (Result ( ValidationError, List ValidationError ) (BackendTask error response))
|
|
229
230
|
getDecoder (Internal.Request.Parser decoder) =
|
|
230
231
|
decoder
|
|
231
232
|
|> Json.Decode.map
|
|
@@ -650,7 +651,6 @@ expectQueryParam name =
|
|
|
650
651
|
maybeParamValue =
|
|
651
652
|
queryString
|
|
652
653
|
|> QueryParams.fromString
|
|
653
|
-
|> QueryParams.toDict
|
|
654
654
|
|> Dict.get name
|
|
655
655
|
|> Maybe.andThen List.head
|
|
656
656
|
in
|
|
@@ -689,7 +689,6 @@ findFirstQueryParam : String -> String -> Maybe String
|
|
|
689
689
|
findFirstQueryParam name queryString =
|
|
690
690
|
queryString
|
|
691
691
|
|> QueryParams.fromString
|
|
692
|
-
|> QueryParams.toDict
|
|
693
692
|
|> Dict.get name
|
|
694
693
|
|> Maybe.andThen List.head
|
|
695
694
|
|
|
@@ -704,7 +703,6 @@ queryParams =
|
|
|
704
703
|
|> Url.fromString
|
|
705
704
|
|> Maybe.andThen .query
|
|
706
705
|
|> Maybe.map QueryParams.fromString
|
|
707
|
-
|> Maybe.map QueryParams.toDict
|
|
708
706
|
|> Maybe.withDefault Dict.empty
|
|
709
707
|
)
|
|
710
708
|
|
|
@@ -879,94 +877,97 @@ fileField_ name =
|
|
|
879
877
|
|> Internal.Request.Parser
|
|
880
878
|
|
|
881
879
|
|
|
880
|
+
runForm : Validation.Validation error parsed kind constraints -> Form.Validated error parsed
|
|
881
|
+
runForm validation =
|
|
882
|
+
Form.Handler.run []
|
|
883
|
+
(Form.Handler.init identity
|
|
884
|
+
(Form.form
|
|
885
|
+
{ combine = validation
|
|
886
|
+
, view = []
|
|
887
|
+
}
|
|
888
|
+
)
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
|
|
882
892
|
{-| -}
|
|
883
893
|
formDataWithServerValidation :
|
|
884
|
-
Form.
|
|
885
|
-
-> Parser (
|
|
894
|
+
Form.Handler.Handler error (BackendTask FatalError (Validation.Validation error combined kind constraints))
|
|
895
|
+
-> Parser (BackendTask FatalError (Result (Form.ServerResponse error) ( Form.ServerResponse error, combined )))
|
|
886
896
|
formDataWithServerValidation formParsers =
|
|
887
897
|
rawFormData
|
|
888
898
|
|> andThen
|
|
889
899
|
(\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 ) ->
|
|
900
|
+
case Form.Handler.run rawFormData_ formParsers of
|
|
901
|
+
Form.Valid decoded ->
|
|
898
902
|
succeed
|
|
899
903
|
(decoded
|
|
900
|
-
|>
|
|
901
|
-
(\
|
|
902
|
-
case
|
|
903
|
-
|
|
904
|
+
|> BackendTask.map
|
|
905
|
+
(\clientValidated ->
|
|
906
|
+
case runForm clientValidated of
|
|
907
|
+
Form.Valid decodedFinal ->
|
|
904
908
|
Ok
|
|
905
|
-
(
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
+
( { persisted =
|
|
910
|
+
{ fields = Just rawFormData_
|
|
911
|
+
, clientSideErrors = Nothing
|
|
912
|
+
}
|
|
913
|
+
, serverSideErrors = Dict.empty
|
|
914
|
+
}
|
|
909
915
|
, decodedFinal
|
|
910
916
|
)
|
|
911
917
|
|
|
912
|
-
|
|
918
|
+
Form.Invalid _ errors2 ->
|
|
913
919
|
Err
|
|
914
|
-
|
|
915
|
-
{ fields = rawFormData_
|
|
916
|
-
,
|
|
917
|
-
maybeErrors
|
|
918
|
-
|> Maybe.map List.NonEmpty.toList
|
|
919
|
-
|> Maybe.withDefault []
|
|
920
|
-
|> Dict.fromList
|
|
920
|
+
{ persisted =
|
|
921
|
+
{ fields = Just rawFormData_
|
|
922
|
+
, clientSideErrors = Just errors2
|
|
921
923
|
}
|
|
922
|
-
|
|
924
|
+
, serverSideErrors = Dict.empty
|
|
925
|
+
}
|
|
923
926
|
)
|
|
924
927
|
)
|
|
925
928
|
|
|
926
|
-
|
|
929
|
+
Form.Invalid _ errors ->
|
|
927
930
|
Err
|
|
928
|
-
|
|
929
|
-
{ fields = rawFormData_
|
|
930
|
-
,
|
|
931
|
-
maybeErrors
|
|
932
|
-
|> Maybe.map List.NonEmpty.toList
|
|
933
|
-
|> Maybe.withDefault []
|
|
934
|
-
|> Dict.fromList
|
|
931
|
+
{ persisted =
|
|
932
|
+
{ fields = Just rawFormData_
|
|
933
|
+
, clientSideErrors = Just errors
|
|
935
934
|
}
|
|
936
|
-
|
|
937
|
-
|
|
935
|
+
, serverSideErrors = Dict.empty
|
|
936
|
+
}
|
|
937
|
+
|> BackendTask.succeed
|
|
938
938
|
|> succeed
|
|
939
939
|
)
|
|
940
940
|
|
|
941
941
|
|
|
942
942
|
{-| -}
|
|
943
943
|
formData :
|
|
944
|
-
Form.
|
|
945
|
-
-> Parser (
|
|
944
|
+
Form.Handler.Handler error combined
|
|
945
|
+
-> Parser ( Form.ServerResponse error, Form.Validated error combined )
|
|
946
946
|
formData formParsers =
|
|
947
947
|
rawFormData
|
|
948
948
|
|> andThen
|
|
949
949
|
(\rawFormData_ ->
|
|
950
|
-
|
|
951
|
-
(
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
950
|
+
case Form.Handler.run rawFormData_ formParsers of
|
|
951
|
+
(Form.Valid _) as validated ->
|
|
952
|
+
( { persisted =
|
|
953
|
+
{ fields = Just rawFormData_
|
|
954
|
+
, clientSideErrors = Just Dict.empty
|
|
955
|
+
}
|
|
956
|
+
, serverSideErrors = Dict.empty
|
|
957
|
+
}
|
|
958
|
+
, validated
|
|
959
|
+
)
|
|
959
960
|
|> succeed
|
|
960
961
|
|
|
961
|
-
( _
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
962
|
+
(Form.Invalid _ maybeErrors) as validated ->
|
|
963
|
+
( { persisted =
|
|
964
|
+
{ fields = Just rawFormData_
|
|
965
|
+
, clientSideErrors = Just maybeErrors
|
|
966
|
+
}
|
|
967
|
+
, serverSideErrors = Dict.empty
|
|
968
|
+
}
|
|
969
|
+
, validated
|
|
970
|
+
)
|
|
970
971
|
|> succeed
|
|
971
972
|
)
|
|
972
973
|
|
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.initOptions))
|
|
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
|
)
|
package/src/Server/SetCookie.elm
CHANGED
|
@@ -2,7 +2,7 @@ module Server.SetCookie exposing
|
|
|
2
2
|
( SetCookie
|
|
3
3
|
, SameSite(..)
|
|
4
4
|
, Options, initOptions
|
|
5
|
-
, withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
|
|
5
|
+
, withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withoutPath, withSameSite
|
|
6
6
|
, toString
|
|
7
7
|
)
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ You can learn more about the basics of cookies in the Web Platform in these help
|
|
|
29
29
|
|
|
30
30
|
@docs Options, initOptions
|
|
31
31
|
|
|
32
|
-
@docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
|
|
32
|
+
@docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withoutPath, withSameSite
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
## Internal
|
|
@@ -143,7 +143,7 @@ initOptions =
|
|
|
143
143
|
{ expiration = Nothing
|
|
144
144
|
, visibleToJavaScript = False
|
|
145
145
|
, maxAge = Nothing
|
|
146
|
-
, path =
|
|
146
|
+
, path = Just "/"
|
|
147
147
|
, domain = Nothing
|
|
148
148
|
, secure = True
|
|
149
149
|
, sameSite = Nothing
|
|
@@ -174,7 +174,7 @@ dynamically). In this API you opt into exposing a cookie you set to JavaScript t
|
|
|
174
174
|
|
|
175
175
|
In general if you can accomplish your goal using HttpOnly cookies (i.e. not using `makeVisibleToJavaScript`) then
|
|
176
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 `
|
|
177
|
+
in session data from cookies in a `BackendTask` (which is resolved server-side before it ever reaches the browser).
|
|
178
178
|
|
|
179
179
|
-}
|
|
180
180
|
makeVisibleToJavaScript : Options -> Options
|
|
@@ -200,6 +200,14 @@ withPath path builder =
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
{-| -}
|
|
204
|
+
withoutPath : Options -> Options
|
|
205
|
+
withoutPath builder =
|
|
206
|
+
{ builder
|
|
207
|
+
| path = Nothing
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
203
211
|
{-| -}
|
|
204
212
|
withDomain : String -> Options -> Options
|
|
205
213
|
withDomain domain builder =
|
package/src/Stub.elm
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Stub exposing (Id, Model, Task(..), map2, nextId)
|
|
2
|
+
|
|
3
|
+
import Json.Decode as Decode
|
|
4
|
+
import Set exposing (Set)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
type alias Id =
|
|
8
|
+
Int
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
type alias Model =
|
|
12
|
+
{ nextId : Id
|
|
13
|
+
, sentIds : Set Id
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
--task : Task error value
|
|
19
|
+
--task =
|
|
20
|
+
-- Pending
|
|
21
|
+
-- (\id -> id)
|
|
22
|
+
-- (\value model -> ( model, Done (Ok value) ))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
type Task error value
|
|
26
|
+
= Pending (Id -> Id) (Decode.Value -> Model -> ( Model, Task error value ))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
map2 : (value1 -> value2 -> combined) -> Task error value1 -> Task error value2 -> Task error combined
|
|
30
|
+
map2 mapFn task1 task2 =
|
|
31
|
+
case ( task1, task2 ) of
|
|
32
|
+
( Pending toId1 _, Pending toId2 _ ) ->
|
|
33
|
+
Pending
|
|
34
|
+
(\id ->
|
|
35
|
+
max (toId1 id) (toId2 id)
|
|
36
|
+
|> nextId
|
|
37
|
+
)
|
|
38
|
+
(\_ _ ->
|
|
39
|
+
Debug.todo ""
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
_ ->
|
|
43
|
+
Debug.todo ""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
nextId : Int -> Int
|
|
47
|
+
nextId id =
|
|
48
|
+
id + 1
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
--(Task toId1 resolve1)
|
|
53
|
+
--(Task toId2 resolve2)
|
|
@@ -130,17 +130,16 @@ nodeRecordToString options { tag, children, facts } =
|
|
|
130
130
|
|> String.join " "
|
|
131
131
|
|> Just
|
|
132
132
|
|
|
133
|
-
boolToString b =
|
|
134
|
-
case b of
|
|
135
|
-
True ->
|
|
136
|
-
"True"
|
|
137
|
-
|
|
138
|
-
False ->
|
|
139
|
-
"False"
|
|
140
|
-
|
|
141
133
|
boolAttributes =
|
|
142
134
|
Dict.toList facts.boolAttributes
|
|
143
|
-
|> List.
|
|
135
|
+
|> List.filterMap
|
|
136
|
+
(\( k, v ) ->
|
|
137
|
+
if v then
|
|
138
|
+
Just k
|
|
139
|
+
|
|
140
|
+
else
|
|
141
|
+
Nothing
|
|
142
|
+
)
|
|
144
143
|
|> String.join " "
|
|
145
144
|
|> Just
|
|
146
145
|
in
|