Current section
Files
Jump to
Current section
Files
lib/wechat/plugs/message_parser.ex
defmodule Wechat.Plugs.MessageParser do
@moduledoc false
defmodule ParseError do
defexception [:message]
end
import Plug.Conn
import SweetXml
import Wechat.Config, only: [token: 0, appid: 0, encoding_aes_key: 0]
alias Wechat.Utils.MessageEncryptor
alias Wechat.Utils.SignatureVerifier
def init(opts) do
opts
end
def call(%{method: "POST"} = conn, opts) do
conn
|> read_body(opts)
|> decode
end
def call(conn, _opts) do
conn
end
defp decode({:ok, body, conn}) do
msg = extract_xml(body)
if msg_encrypted?(conn.params) do
case verify_msg_signature(msg, conn.query_params) do
{:ok, msg_encrypted} ->
msg_decrypted = decrypt(msg_encrypted)
%{conn | body_params: msg_decrypted}
:error ->
raise ParseError, "invalid msg_signature"
end
else
%{conn | body_params: msg}
end
rescue
_ ->
conn
|> send_resp(400, "")
|> halt
end
defp verify_msg_signature(%{"Encrypt" => msg_encrypted},
%{"timestamp" => timestamp, "nonce" => nonce,
"msg_signature" => signature}) do
args = [token(), timestamp, nonce, msg_encrypted]
if SignatureVerifier.verify?(args, signature) do
{:ok, msg_encrypted}
else
:error
end
end
defp decrypt(msg_encrypted) do
appid = appid()
encoding_aes_key = encoding_aes_key()
case MessageEncryptor.decrypt(msg_encrypted, encoding_aes_key) do
{^appid, msg_decrypted} ->
extract_xml(msg_decrypted)
_ ->
raise ParseError, "invalid appid"
end
end
defp msg_encrypted?(%{"encrypt_type" => "aes"}), do: true
defp msg_encrypted?(_), do: false
def extract_xml(doc) do
doc
|> xpath(~x"//xml/*"l)
|> Enum.into(%{}, &({xpath(&1, ~x"name(.)"s), xpath(&1, ~x"text()"s)}))
end
end