Guest User

Untitled

a guest
Dec 14th, 2017
148
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.29 KB | None | 0 0
  1. defmodule RMAS.WebServer do
  2. ## setup
  3. @moduledoc false
  4. require Logger
  5. use Raxx.Server
  6. use Raxx.Static, "../dist"
  7.  
  8. ## variables
  9. {:ok, contents} = File.read("./dist/index.html")
  10. @contents contents
  11. @format [pretty: true, limit: :infinity, width: :infinity]
  12. @jwt_key "some-really-secret-guid"
  13.  
  14. ## handle requests
  15. @doc "
  16. - handle OPTIONS for CORS pre-flight
  17. - handles /sse
  18. - handle /api
  19. - fallback for regular content"
  20. def handle_head(r, s) do
  21. Logger.info("[#{inspect self()}] #{inspect r, @format}")
  22. try do
  23. case r do
  24. %{method: :TRACE} -> trace_request(r, s)
  25. %{path: ["api" | _], method: :OPTIONS} -> preflight_request(r, s)
  26. # %{path: ["api" | _]} -> api_request(r, s)
  27. # %{path: ["sse" | _]} -> sse_request(r, s)
  28. # _ -> general_request(r, s)
  29. _ -> {[], s}
  30. end
  31. rescue
  32. ex ->
  33. Logger.error("[#{inspect self()}] #{inspect(ex)}\n#{Exception.format_stacktrace(System.stacktrace)}")
  34. {[response(500) |> set_body(false)], s}
  35. end
  36. end
  37. def handle_info(r, s) do
  38. Logger.warn "#{inspect(r, @format)}"
  39. r
  40. end
  41. def handle_data(data, {r, buffer, s}) do
  42. r = %{r | body: buffer <> data}
  43. case r do
  44. %{path: ["api" | _]} -> api_request(r, s)
  45. _ -> general_request(r, s)
  46. end
  47. end
  48.  
  49. defp trace_request(%{body: body}, state) do
  50. outbound = [
  51. response(200)
  52. |> set_body(body)
  53. ]
  54. {outbound, state}
  55. end
  56. defp preflight_request(%{headers: headers}, state) do # OPTIONS -> handle pre-filght correctly
  57. h = Enum.into(headers, %{})
  58. m = h["access-control-request-method"]
  59.  
  60. cors = [
  61. {"access-control-allow-credentials", "true"},
  62. {"access-control-allow-headers", "authorization,content-type"},
  63. {"access-control-allow-origin", h["origin"]}
  64. ]
  65.  
  66. cors =
  67. if m in ["DELETE", "PUT", "PATCH"] do
  68. cors ++ [{"access-control-allow-methods", m}]
  69. else
  70. cors
  71. end
  72.  
  73. outbound = [
  74. response(204)
  75. |> set_headers(cors)
  76. |> set_body(false)
  77. ]
  78.  
  79. Logger.info("[#{inspect self()}] #{inspect outbound, @format}")
  80.  
  81. {outbound, state}
  82. end
  83. def api_request(req, state) do # api -> cors, request/response + json, check token
  84. outbound = [
  85. proc_request(req)
  86. ]
  87. {outbound, state}
  88. end
  89. defp sse_request(%{headers: headers}, state) do # sse
  90. h = Enum.into(headers, %{})
  91.  
  92. outbound = [
  93. response(200)
  94. |> set_header("content-type", "text/event-stream")
  95. |> set_header("access-control-allow-origin", h["origin"])
  96. |> set_header("access-control-allow-credentials", "true")
  97. |> set_body(true)
  98. ]
  99. {outbound, state}
  100. end
  101. defp general_request(_req, state) do # fallback -> request/response
  102. outbound = [
  103. response(200)
  104. |> set_header("content-type", "text/html; charset=utf-8")
  105. |> set_body(@contents)
  106. ]
  107. {outbound, state}
  108. end
  109.  
  110. ## Report Runner
  111. defp proc_request(%{method: :POST, path: ["api", "report-runner"], body: data}) do
  112. with {:ok, %{userid: userid, code: code, date: date}} <- Antidote.decode(data, keys: :atoms) do
  113. RMAS.Server.run(userid, code, date, true)
  114. cors_response(200)
  115. else
  116. e ->
  117. Logger.warn("#{inspect(e, @format)}")
  118. cors_response(400)
  119. end
  120. end
  121.  
  122. ## Auth
  123. defp proc_request(%{method: :POST, path: ["api", "auth", "login"], body: data}) do
  124. with {:ok, m} <- Antidote.decode(data, keys: :atoms),
  125. {:ok, 1, _, [u]} <- DB.Users.list(%{email: m.username}),
  126. {:ok, true} <- verify_password(u.password, m.password) do
  127. token = generate_token(u.user_id)
  128. r = %{user: un_struct(u), token: token}
  129. json = Antidote.encode!(r)
  130. cors_response(json)
  131. else
  132. {:error, msg} ->
  133. Logger.warn("#{inspect(msg, @format)}")
  134. cors_response(:unauthorized)
  135. _ -> cors_response(:unauthorized)
  136. end
  137. end
  138. defp proc_request(%{method: :GET, path: ["api", "auth", "test"]}) do
  139. cors_response(%{status: :OK})
  140. end
  141.  
  142. ## Other
  143. defp proc_request(%{method: :POST, path: ["api", "clearlogs"]}), do: run(204, DB.execute("truncate table systemlog"))
  144. defp proc_request(%{method: :GET, path: ["api", "systemlog"]}), do: run(200, DB.execute("select id, l.user_id, isnull(u.name,'SYSTEM') runner, batch, report, report_dt, status, dt_start, dt_end, DATEDIFF(MS, dt_start, dt_end) as [ms_durn] from systemlog l left join users u on u.user_id = l.user_id order by batch desc, id"))
  145. defp proc_request(%{method: :GET, path: ["api", entity]}), do: run(200, DB.execute("select * from #{entity}"))
  146. defp proc_request(%{method: :GET, path: ["api", entity, id]}), do: run(200, DB.select(id, entity))
  147.  
  148. ## Users
  149. defp proc_request(%{method: :GET, path: ["api", "users"]}), do: run(200, DB.Users.list())
  150. defp proc_request(%{method: :GET, path: ["api", "users", id]}), do: run(200, DB.Users.get(id))
  151. defp proc_request(%{method: :POST, path: ["api", "users"], body: data}) do
  152. u = Antidote.decode!(data, keys: :atoms)
  153. u = %{u | password: hash_password("password"), reset_required: true}
  154. with {:ok, _, _, u} <- DB.Users.create(u) do
  155. cors_response(un_struct(u), 201, [{"location", "/api/users/#{u.user_id}"}])
  156. else
  157. e ->
  158. Logger.warn("#{inspect(e, @format)}")
  159. cors_response(400)
  160. end
  161. end
  162. defp proc_request(%{method: :PUT, path: ["api", "users", id], body: data}) do
  163. {:ok, 1, _, u} = DB.Users.get(id)
  164.  
  165. o = Antidote.decode!(data, keys: :atoms)
  166.  
  167. {password, reset} =
  168. if o.password === "" or o.password === "0000000000" do
  169. {u.password, o.reset_required}
  170. else
  171. {hash_password(o.password), false}
  172. end
  173.  
  174. o = %{o | password: password, reset_required: reset}
  175.  
  176. run(200, DB.Users.update(o))
  177. end
  178. defp proc_request(%{method: :DELETE, path: ["api", "users", id]}), do: run(204, DB.Users.delete(id))
  179.  
  180. ## fallback
  181. defp proc_request(%{path: ["api"]}), do: cors_response("api-root")
  182. defp proc_request(%{path: ["api" | _]}), do: cors_response(404)
  183.  
  184. ## internal
  185. ## Security
  186. def hash_password(password) do
  187. # create a unique salt and salt string
  188. salt = :crypto.strong_rand_bytes(16)
  189. salt_string = Base.encode64(salt)
  190.  
  191. # create the hash passing in the password, the salt, the hmac, the number of iterations and the bytes to be returned
  192. hash_bytes = pbkdf2(password, salt)
  193. hash = Base.encode64(hash_bytes)
  194.  
  195. "#{salt_string}|#{hash}"
  196. end
  197. def verify_password(hashed_password, provided_password) do
  198. # split the hash from the salt, like we stored it before
  199. [stored_salt, stored_hash] = String.split(hashed_password, "|")
  200.  
  201. # get the byte arrays of the strings
  202. salt_bytes = Base.decode64!(stored_salt)
  203.  
  204. # create the hash exactly how we did before, but with the stored salt
  205. calc_hash_bytes = pbkdf2(provided_password, salt_bytes)
  206.  
  207. # string calc_hash = Convert.ToBase64String(calc_hash_bytes);
  208. calc_hash = Base.encode64(calc_hash_bytes)
  209.  
  210. {:ok, calc_hash === stored_hash}
  211. end
  212.  
  213. defp pbkdf2(password, salt), do: pbkdf2(password, salt, :sha256, 20000, 32, 1, [], 0)
  214. defp pbkdf2(_password, _salt, _digest, _rounds, dklen, _block_index, acc, length) when length >= dklen do
  215. key = acc |> Enum.reverse |> IO.iodata_to_binary
  216. <<bin::binary-size(dklen), _::binary>> = key
  217. bin
  218. end
  219. defp pbkdf2(password, salt, digest, rounds, dklen, block_index, acc, length) do
  220. initial = :crypto.hmac(digest, password, <<salt::binary, block_index::integer-size(32)>>)
  221. block = iterate(password, digest, rounds - 1, initial, initial)
  222. pbkdf2(password, salt, digest, rounds, dklen, block_index + 1,
  223. [block | acc], byte_size(block) + length)
  224. end
  225.  
  226. defp iterate(_password, _digest, 0, _prev, acc), do: acc
  227. defp iterate(password, digest, round, prev, acc) do
  228. next = :crypto.hmac(digest, password, prev)
  229. iterate(password, digest, round - 1, next, :crypto.exor(next, acc))
  230. end
  231.  
  232. defp generate_token(user_id) do
  233. JsonWebToken.sign(%{user_id: user_id, jti: UUID.uuid4()}, %{key: @jwt_key})
  234. end
  235.  
  236. ## utility
  237. defp un_struct(l) when is_list(l), do: Enum.map(l, &un_struct/1)
  238. defp un_struct(s = %_{}), do: Map.from_struct(s)
  239. defp un_struct(s), do: s
  240.  
  241. defp cors_response(code) when is_atom(code), do: cors_response(nil, code)
  242. defp cors_response(code) when is_integer(code), do: cors_response(nil, code)
  243. defp cors_response(body, code \\ :ok, headers \\ [])
  244. defp cors_response(body, code, headers), do: api_response(body, code, headers ++ [{"access-control-allow-origin", "*"}, {"access-control-allow-credentials", "true"}])
  245. defp api_response(body, code, headers) when is_list(headers) do
  246. response(code)
  247. |> set_header("content-type", "application/json; charset=utf-8")
  248. |> set_headers(headers)
  249. |> check_body(body)
  250. end
  251. defp sse_response(body, headers) do
  252. response(200)
  253. |> set_headers(headers ++ [{"access-control-allow-credentials", "true"}, {"content-type", "text/event-stream"}])
  254. |> set_body(body)
  255. end
  256. defp check_body(res, nil), do: res
  257. defp check_body(res, b) when is_binary(b), do: set_body(res, b)
  258. defp check_body(res, b), do: set_body(res, Antidote.encode!(b))
  259.  
  260. defp run(code, {:ok, _count, _durn, nil}), do: cors_response(code)
  261. defp run(code, {:ok, _count, _durn, value}), do: cors_response(un_struct(value), code)
  262. defp run(_code, {:error, msg, _sql}), do: cors_response(%{error_msg: msg},400)
  263. defp run(_code, {:error, x}), do: cors_response(%{error_msg: "#{inspect x}"},400)
  264. end
Add Comment
Please, Sign In to add comment