codedge-llc / pigeon

iOS and Android push notifications for Elixir
https://hex.pm/packages/pigeon
MIT License
645 stars 145 forks source link

Trouble Exporting APNs Auth Key p8 as Env Var #138

Closed wfgilman closed 5 years ago

wfgilman commented 5 years ago

Loving this library. However, I'm having trouble exporting the contents of my APNs Auth Key as an environment variable for my production deployment. The docs say:

:key - Created and downloaded via your developer account. Like :cert this can be a file path, file contents string or tuple

My Auth Key comes in the format:

----- BEGIN PRIVATE KEY ------
LINE 1 CODE
LINE 2 CODE
LINE 3 CODE
----- END PRIVATE KEY ------

I'm having trouble getting the correct "file contents string" exported. If I concatenate the contents between the ----- BEGIN PRIVATE KEY ----- and ----- END PRIVATE KEY ---- and pass as a string, I get the following error on Pigeon.APNS.push/1:

** (FunctionClauseError) no function clause matching in Joken.Signer.es/2
(joken) lib/joken/signer.ex:71: Joken.Signer.es("ES256", [])
(pigeon) lib/pigeon/apns/jwt_config.ex:223: Pigeon.Configurable.Pigeon.APNS.JWTConfig.generate_apns_jwt/2
(pigeon) lib/pigeon/apns/jwt_config.ex:203: Pigeon.Configurable.Pigeon.APNS.JWTConfig.put_bearer_token/2
(pigeon) lib/pigeon/connection.ex:142: Pigeon.Connection.send_push/3
(elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3
(pigeon) lib/pigeon/connection.ex:97: Pigeon.Connection.handle_events/3
(gen_stage) lib/gen_stage.ex:2329: GenStage.consumer_dispatch/6
(stdlib) gen_server.erl:637: :gen_server.try_dispatch/4

Any guidance on handling the new p8 keys as environment variables?

hpopp commented 5 years ago

You'll want to use a runtime worker configuration. Many packages implement this differently, but pigeon accepts any function that returns a valid worker config struct.

# config/prod.exs

config :pigeon,
  workers: [
    {YourApp.Pigeon, :apns_jwt}
  ]

And then define a module somewhere in your project:

defmodule YourApp.Pigeon do
  @moduledoc false

  alias Pigeon.APNS

  def apns_jwt do
    APNS.JWTConfig.new(
      name: :apns_default,
      key: System.get_env("APNS_JWT_KEY"),
      key_identifier: System.get_env("APNS_JWT_KEY_IDENTIFIER"),
      team_id: System.get_env("APNS_JWT_TEAM_ID"),
      mode: :prod
    )
  end
end
wfgilman commented 5 years ago

Thanks for the tip. The apns_jwt/0 function is successfully reading my variables in production, but the configuration isn't being read properly. Pigeon.Configurable.Pigeon.APNS.JWTConfig.put_bearer_token/2 is raising because it's passing in an empty list where the configuration should be. I can't seem to debug it.

Here's my files and stacktrace from running in IEx. My application is called Push. dev.exs

use Mix.Config

config :pigeon,
  workers: [
    {Push.Pigeon, :apns_jwt}
  ]

pigeon.ex

defmodule Push.Pigeon do
  def apns_jwt do
    Pigeon.APNS.JWTConfig.new(
      name: :apns_default,
      key: System.get_env("APNS_JWT_KEY"),
      key_identifier: System.get_env("APNS_JWT_KEY_IDENTIFIER"),
      team_id: System.get_env("APNS_JWT_TEAM_ID"),
      mode: :prod
    )
  end
end

IEx

iex(2)> Push.Pigeon.apns_jwt
%Pigeon.APNS.JWTConfig{
  key: "MIG...",
  key_identifier: "D4...",
  keyfile: nil,
  name: :apns_default,
  ping_period: 600000,
  port: 443,
  reconnect: false,
  team_id: "QQ...",
  uri: "api.push.apple.com"
}
iex(3)> n = Pigeon.APNS.Notification.new "test message", "2db...db7", "test"
%Pigeon.APNS.Notification{
  collapse_id: nil,
  device_token: "2db...db7",
  expiration: nil,
  id: nil,
  payload: %{"aps" => %{"alert" => "test message"}},
  response: nil,
  topic: "test"
}
iex(4)> Pigeon.APNS.push n
[error] GenServer #PID<0.432.0> terminating
** (FunctionClauseError) no function clause matching in Joken.Signer.es/2
    (joken) lib/joken/signer.ex:71: Joken.Signer.es("ES256", [])
    (pigeon) lib/pigeon/apns/jwt_config.ex:223: Pigeon.Configurable.Pigeon.APNS.JWTConfig.generate_apns_jwt/2
    (pigeon) lib/pigeon/apns/jwt_config.ex:203: Pigeon.Configurable.Pigeon.APNS.JWTConfig.put_bearer_token/2
    (pigeon) lib/pigeon/connection.ex:142: Pigeon.Connection.send_push/3
    (elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3
    (pigeon) lib/pigeon/connection.ex:97: Pigeon.Connection.handle_events/3
    (gen_stage) lib/gen_stage.ex:2329: GenStage.consumer_dispatch/6
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_consumer", {#PID<0.380.0>, #Reference<0.426290155.797966339.85590>}, [{:push, %Pigeon.APNS.Notification{collapse_id: nil, device_token: "2db...db7", expiration: nil, id: nil, payload: %{"aps" => %{"alert" => "test message"}}, response: nil, topic: "test"}, [on_response: #Function<3.54539434/1 in Pigeon.APNS.sync_push/2>]}]}
%Pigeon.APNS.Notification{
  collapse_id: nil,
  device_token: "2db...db7",
  expiration: nil,
  id: nil,
  payload: %{"aps" => %{"alert" => "test message"}},
  response: :timeout,
  topic: "test"
}

I'm having a heck of a time figuring out where the problem is. Any thoughts?

talklittle commented 5 years ago

Your original issue says

If I concatenate the contents between the ----- BEGIN PRIVATE KEY ----- and ----- END PRIVATE KEY ---- and pass as a string

This sounds wrong as described, you probably shouldn't be concatenating anything. Just read the file contents exactly as-is, newlines and all, and including the BEGIN and END lines.

wfgilman commented 5 years ago

@talklittle ah, gotcha. Unrelated, but do you know how the commands for exporting file contents to a variable? I've got to set the contents as an env var

talklittle commented 5 years ago

I guess in bash: MY_VAR=$(cat file.p8)

Let's back up a minute though. You verified the p8 works when specified as a file path right? If it doesn't work even when specified as a file path, there might be an incompatibility with JOSE or the Erlang public_key module, and you might have to convert the p8 to a different, supported PEM. (Or download a different format from Apple.)

wfgilman commented 5 years ago

Yeah, it works great as a filepath. I’ve just been having a lot of trouble referencing the contents of the p8 file correctly.

On Mon, Jan 14, 2019 at 10:16 PM Andrew Shu notifications@github.com wrote:

I guess in bash: MY_VAR=$(cat file.p8)

Let's back up a minute though. You verified the p8 works when specified as a file path right? If it doesn't work even when specified as a file path, there might be an incompatibility with JOSE or the Erlang public_key module, and you might have to convert the p8 to a different, supported PEM. (Or download a different format from Apple.)

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/codedge-llc/pigeon/issues/138#issuecomment-454279242, or mute the thread https://github.com/notifications/unsubscribe-auth/ASZ7ODIAGvQJDIy49---FZOovlj4QlA3ks5vDXJTgaJpZM4Z7toy .

wfgilman commented 5 years ago

I got it working, that was tricky. For what it's worth, bash doesn't like the ---- in the p8 file. You can escape it by including -- when setting the variable: export -- APNS_JWT_KEY="$(cat path/to/priv/key.p8)"