Try all secrets in list for decoding signed cookies.

This commit is contained in:
Dillon Kearns 2022-01-20 19:09:37 -08:00
parent b7185adc39
commit 08e635cbeb
2 changed files with 59 additions and 95 deletions

View File

@ -57,41 +57,23 @@ withSession config decoder toRequest =
Request.cookie config.name Request.cookie config.name
|> Request.map |> Request.map
(\maybeSessionCookie -> (\maybeSessionCookie ->
--DataSource.succeed
-- (case maybeSessionCookie of
-- Just sessionCookie ->
-- Debug.todo ""
--
-- Nothing ->
-- Debug.todo ""
-- )
let let
result : Result String decoded decrypted : DataSource (Result String decoded)
result =
--Err ""
case maybeSessionCookie of
Nothing ->
Err "TODO"
Just sessionCookie ->
OptimizedDecoder.decodeString decoder sessionCookie
|> Result.mapError Json.Decode.errorToString
--decrypted : DataSource (Dict String Json.Decode.Value)
decrypted = decrypted =
case maybeSessionCookie of case maybeSessionCookie of
Just sessionCookie -> Just sessionCookie ->
decrypt decoder sessionCookie decrypt config.secrets decoder sessionCookie
|> DataSource.map Ok |> DataSource.map Ok
Nothing -> Nothing ->
Err "TODO" Err "TODO"
|> DataSource.succeed |> DataSource.succeed
decryptedFull : DataSource (Dict String OptimizedDecoder.Value)
decryptedFull = decryptedFull =
maybeSessionCookie maybeSessionCookie
|> Maybe.map |> Maybe.map
(\sessionCookie -> decrypt (OptimizedDecoder.dict OptimizedDecoder.value) sessionCookie) (\sessionCookie -> decrypt config.secrets (OptimizedDecoder.dict OptimizedDecoder.value) sessionCookie)
|> Maybe.withDefault (DataSource.succeed Dict.empty) |> Maybe.withDefault (DataSource.succeed Dict.empty)
in in
decryptedFull decryptedFull
@ -100,6 +82,7 @@ withSession config decoder toRequest =
DataSource.andThen DataSource.andThen
(\( sessionUpdate, response ) -> (\( sessionUpdate, response ) ->
let let
encodedCookie : Json.Encode.Value
encodedCookie = encodedCookie =
Session.setValues sessionUpdate cookieDict Session.setValues sessionUpdate cookieDict
in in
@ -126,11 +109,6 @@ withSession config decoder toRequest =
encrypt : Secrets.Value (List String) -> Json.Encode.Value -> DataSource.DataSource String encrypt : Secrets.Value (List String) -> Json.Encode.Value -> DataSource.DataSource String
encrypt secrets input = encrypt secrets input =
let
decoder : OptimizedDecoder.Decoder String
decoder =
OptimizedDecoder.string
in
DataSource.Http.request DataSource.Http.request
(secrets (secrets
|> Secrets.map |> Secrets.map
@ -140,31 +118,44 @@ encrypt secrets input =
, headers = [] , headers = []
-- TODO pass through secrets here -- TODO pass through secrets here
, body = DataSource.Http.jsonBody input , body =
DataSource.Http.jsonBody
(Json.Encode.object
[ ( "values", input )
, ( "secret"
, Json.Encode.string
(secretList
|> List.head
-- TODO use different default - require non-empty list?
|> Maybe.withDefault ""
)
)
]
)
} }
) )
) )
decoder OptimizedDecoder.string
decrypt : Secrets.Value (List String) -> OptimizedDecoder.Decoder a -> String -> DataSource a
--decrypt : String -> DataSource.DataSource (Dict String Json.Decode.Value) decrypt secrets decoder input =
decrypt : OptimizedDecoder.Decoder a -> String -> DataSource a
decrypt decoder input =
--let
-- decoder : OptimizedDecoder.Decoder (Dict String Json.Decode.Value)
-- decoder =
-- OptimizedDecoder.dict OptimizedDecoder.value
--in
DataSource.Http.request DataSource.Http.request
(Secrets.succeed (secrets
{ url = "port://decrypt" |> Secrets.map
, method = "GET" (\secretList ->
, headers = [] { url = "port://decrypt"
, body = DataSource.Http.jsonBody (Json.Encode.string input) , method = "GET"
} , headers = []
, body =
DataSource.Http.jsonBody
(Json.Encode.object
[ ( "input", Json.Encode.string input )
, ( "secrets", Json.Encode.list Json.Encode.string secretList )
]
)
}
)
) )
decoder decoder
@ -201,35 +192,11 @@ data routeParams =
-- ("hello " ++ requestData.username ++ "!") -- ("hello " ++ requestData.username ++ "!")
-- |> DataSource.succeed -- |> DataSource.succeed
-- ), -- ),
--, withSession
-- { name = "__session"
-- , secrets =
-- Secrets.succeed
-- (\sessionSecret -> [ sessionSecret ])
-- |> Secrets.with "SESSION_SECRET"
-- , sameSite = "lax" -- TODO custom type
-- , codec =
-- -- TODO use custom codec API, allowing you to retrieve fields, decode them, and set fields with flash
-- Codec.object identity
-- |> Codec.field "userId" identity Codec.string
-- |> Codec.buildObject
-- }
-- (\userIdResult ->
-- case userIdResult of
-- Err error ->
-- Debug.todo ""
--
-- Ok userId ->
-- Request.succeed
-- (DataSource.succeed
-- ( userId, Server.Response.temporaryRedirect "/login" )
-- )
-- )
withSession withSession
{ name = "mysession" { name = "mysession"
, secrets = , secrets =
Secrets.succeed Secrets.succeed
(\sessionSecret -> [ sessionSecret ]) (\sessionSecret -> [ "secret4", "secret3", "secret2", sessionSecret ])
|> Secrets.with "SESSION_SECRET" |> Secrets.with "SESSION_SECRET"
, sameSite = "lax" -- TODO custom type , sameSite = "lax" -- TODO custom type
} }

View File

@ -2,6 +2,7 @@ const path = require("path");
const undici = require("undici"); const undici = require("undici");
const objectHash = require("object-hash"); const objectHash = require("object-hash");
const kleur = require("kleur"); const kleur = require("kleur");
const cookie = require("cookie-signature");
/** /**
* To cache HTTP requests on disk with quick lookup and insertion, we store the hashed request. * To cache HTTP requests on disk with quick lookup and insertion, we store the hashed request.
@ -56,26 +57,13 @@ function lookupOrPerform(mode, rawRequest, hasFsAccess) {
} catch (e) {} } catch (e) {}
if (request.url === "port://encrypt") { if (request.url === "port://encrypt") {
const cookie = require("cookie-signature");
console.log(
"@@@signing",
rawRequest.body.args[0],
typeof rawRequest.body.args[0]
);
console.log(
"signed",
cookie.sign(
JSON.stringify(rawRequest.body.args[0], null, 0),
"abcdefg"
)
);
try { try {
await fs.promises.writeFile( await fs.promises.writeFile(
responsePath, responsePath,
JSON.stringify( JSON.stringify(
cookie.sign( cookie.sign(
JSON.stringify(rawRequest.body.args[0], null, 0), JSON.stringify(rawRequest.body.args[0].values, null, 0),
"abcdefg" rawRequest.body.args[0].secret
) )
) )
); );
@ -91,18 +79,14 @@ function lookupOrPerform(mode, rawRequest, hasFsAccess) {
}); });
} }
} else if (request.url === "port://decrypt") { } else if (request.url === "port://decrypt") {
const cookie = require("cookie-signature");
console.log("@@@[0]", rawRequest.body.args[0]);
try { try {
console.log(
"unsigned",
cookie.unsign(rawRequest.body.args[0], "abcdefg")
);
// TODO if unsign returns `false`, need to have an `Err` in Elm because decryption failed // TODO if unsign returns `false`, need to have an `Err` in Elm because decryption failed
await fs.promises.writeFile( const signed = tryDecodeCookie(
responsePath, rawRequest.body.args[0].input,
cookie.unsign(rawRequest.body.args[0], "abcdefg") rawRequest.body.args[0].secrets
); );
await fs.promises.writeFile(responsePath, signed);
resolve(responsePath); resolve(responsePath);
} catch (e) { } catch (e) {
reject({ reject({
@ -181,6 +165,19 @@ function lookupOrPerform(mode, rawRequest, hasFsAccess) {
}); });
} }
function tryDecodeCookie(input, secrets) {
if (secrets.length > 0) {
const signed = cookie.unsign(input, secrets[0]);
if (signed) {
return signed;
} else {
return tryDecodeCookie(input, secrets.slice(1));
}
} else {
return null;
}
}
/** /**
* @param {{url: string; headers: {[x: string]: string}; method: string; body: Body } } elmRequest * @param {{url: string; headers: {[x: string]: string}; method: string; body: Body } } elmRequest
*/ */