restroom.ex (4126B)
1 # Zenflows is designed to implement the Valueflows vocabulary, 2 # written and maintained by srfsh <info@dyne.org>. 3 # Copyright (C) 2021-2023 Dyne.org foundation <foundation@dyne.org>. 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Affero General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 defmodule Zenflows.Restroom do 19 @moduledoc """ 20 A module to interact with Restroom instances over (for now) HTTP. 21 """ 22 23 def child_spec(_) do 24 Supervisor.child_spec( 25 {Zenflows.HTTPC, 26 name: __MODULE__, 27 scheme: scheme(), 28 host: host(), 29 port: port(), 30 }, 31 id: __MODULE__) 32 end 33 34 @doc """ 35 Returns `true` when `left` and `right` are equal, `false` otherwise. 36 """ 37 @spec byte_equal?(binary(), binary()) :: boolean() 38 def byte_equal?(left, right) do 39 data = %{left: Base.encode64(left), right: Base.encode64(right)} 40 case exec("byte_equal", data) do 41 {:ok, %{"output" => ["1"]}} -> true 42 _ -> false 43 end 44 end 45 46 @doc """ 47 Given the GraphQL `body`, its `signature`, and `pubkey` of the user who 48 executes the query, verify that everything matches. 49 """ 50 @spec verify_graphql(binary(), String.t(), String.t()) :: :ok | {:error, String.t()} 51 def verify_graphql(body, signature, pubkey) do 52 data = %{ 53 "gql" => Base.encode64(body), 54 "eddsa_signature" => signature, 55 "eddsa_public_key" => pubkey, 56 } 57 case exec("verify_graphql", data) do 58 {:ok, %{"output" => ["1"]}} -> :ok 59 {:error, reason} -> {:error, reason} 60 end 61 end 62 63 @doc """ 64 See https://github.com/dyne/keypairoom for details. 65 """ 66 @spec keypairoom_server(map()) :: {:ok, String.t()} | {:error, term()} 67 def keypairoom_server(data) do 68 data = %{ 69 "userData" => data, 70 "serverSideSalt" => salt(), 71 } 72 case exec("keypairoomServer-6-7", data) do 73 {:ok, %{"seedServerSideShard.HMAC" => hmac}} -> {:ok, hmac} 74 {:error, reason} -> {:error, reason} 75 end 76 end 77 78 # Execute a Zencode specified by `name` with JSON data `data`. 79 @spec exec(String.t(), map()) :: {:ok, map()} | {:error, term()} 80 def exec(name, post_data) do 81 request(&Zenflows.HTTPC.request(__MODULE__, &1, &2, &3, &4), 82 name, post_data) 83 end 84 85 @doc """ 86 Given the request function (wrapper of Zenflows.HTTPC.request), the path 87 and the data to post, it makes the request and parse the result. 88 """ 89 @spec request(fun(), String.t(), map()) :: {:ok, map()} | {:error, term()} 90 def request(request_fn, path, post_data) do 91 hdrs = [{"content-type", "application/json"}] 92 93 with {:ok, post_body} <- Jason.encode(%{data: post_data}), 94 {:ok, %{status: stat, data: body}} when stat == 200 or stat == 500 <- 95 request_fn.("POST", "/api/#{path}", hdrs, post_body), 96 {:ok, data} <- Jason.decode(body) do 97 if stat == 200 do 98 {:ok, data} 99 else 100 {:error, data |> Map.fetch!("zenroom_errors") |> Map.fetch!("logs")} 101 end 102 else 103 {:ok, %{status: stat, data: body}} -> 104 {:error, "the http call result in non-200 status code #{stat}: #{inspect(body)}"} 105 106 other -> other 107 end 108 end 109 110 # Return the salt from the configs. 111 @spec salt() :: String.t() 112 defp salt() do 113 Keyword.fetch!(conf(), :room_salt) 114 end 115 116 # Return the scheme of restroom from the configs. 117 @spec scheme() :: :http | :https 118 defp scheme() do 119 Keyword.fetch!(conf(), :room_uri).scheme 120 end 121 122 # Return the hostname of restroom from the configs. 123 @spec host() :: String.t() 124 defp host() do 125 Keyword.fetch!(conf(), :room_uri).host 126 end 127 128 # Return the port of restroom from the configs. 129 @spec port() :: non_neg_integer() 130 defp port() do 131 Keyword.fetch!(conf(), :room_uri).port 132 end 133 134 # Return the application configurations of this module. 135 @spec conf() :: Keyword.t() 136 defp conf() do 137 Application.fetch_env!(:zenflows, __MODULE__) 138 end 139 end