diff --git a/lib/postland.ex b/lib/postland.ex index 4815dd2..101b980 100644 --- a/lib/postland.ex +++ b/lib/postland.ex @@ -13,11 +13,30 @@ defmodule Postland do :crypto.strong_rand_bytes(12) |> Base.encode64() end + def generate_keys(bits \\ 4096) do + {:RSAPrivateKey, _, modulus, publicExponent, _, _, _, _exponent1, _, _, _otherPrimeInfos} = + rsa_private_key = :public_key.generate_key({:rsa, bits, 65537}) + + rsa_public_key = {:RSAPublicKey, modulus, publicExponent} + + pem_entry = :public_key.pem_entry_encode(:RSAPrivateKey, rsa_private_key) + private_key = :public_key.pem_encode([pem_entry]) + + pem_entry = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, rsa_public_key) + public_key = :public_key.pem_encode([pem_entry]) + + %{public: public_key, private: private_key} + end + def on_boot_setup() do if !setup?() do + %{public: public_key, private: private_key} = generate_keys() + attrs = %{ username: "welcome", - password: temporary_password() + password: temporary_password(), + private_key: private_key, + public_key: public_key } {:ok, _user} = Accounts.register_user(attrs) diff --git a/lib/postland/accounts/user.ex b/lib/postland/accounts/user.ex index b4a0eb2..bf9d3fd 100644 --- a/lib/postland/accounts/user.ex +++ b/lib/postland/accounts/user.ex @@ -8,6 +8,8 @@ defmodule Postland.Accounts.User do field :hashed_password, :string, redact: true field :current_password, :string, virtual: true, redact: true field :confirmed_at, :utc_datetime + field :private_key, :string + field :public_key, :string timestamps(type: :utc_datetime) end @@ -31,7 +33,7 @@ defmodule Postland.Accounts.User do """ def registration_changeset(user, attrs, opts \\ []) do user - |> cast(attrs, [:username, :password]) + |> cast(attrs, [:username, :password, :public_key, :private_key]) |> validate_username() |> validate_password(opts) end diff --git a/lib/postland_web/controllers/actor_controller.ex b/lib/postland_web/controllers/actor_controller.ex new file mode 100644 index 0000000..1404f7f --- /dev/null +++ b/lib/postland_web/controllers/actor_controller.ex @@ -0,0 +1,7 @@ +defmodule PostlandWeb.ActorController do + use PostlandWeb, :controller + + def get(conn, _params) do + render(conn, :actor, %{}) + end +end diff --git a/lib/postland_web/controllers/actor_json.ex b/lib/postland_web/controllers/actor_json.ex new file mode 100644 index 0000000..0f75937 --- /dev/null +++ b/lib/postland_web/controllers/actor_json.ex @@ -0,0 +1,25 @@ +defmodule PostlandWeb.ActorJSON do + use Phoenix.VerifiedRoutes, endpoint: PostlandWeb.Endpoint, router: PostlandWeb.Router + + alias Postland.Accounts + + def render("actor.json", _assigns) do + user = Accounts.solo_user() + + %{ + "@context" => [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1" + ], + "id" => url(~p"/actor"), + "type" => "Person", + "preferredUsername" => user.username, + "inbox" => url(~p"/inbox"), + "publicKey" => %{ + "id" => url(~p"/actor#main-key"), + "owner" => url(~p"/actor"), + "publicKeyPem" => user.public_key + } + } + end +end diff --git a/lib/postland_web/controllers/webfinger_controller.ex b/lib/postland_web/controllers/webfinger_controller.ex new file mode 100644 index 0000000..e78f240 --- /dev/null +++ b/lib/postland_web/controllers/webfinger_controller.ex @@ -0,0 +1,9 @@ +defmodule PostlandWeb.WebfingerController do + use PostlandWeb, :controller + + alias Postland.Accounts + + def get(conn, _params) do + render(conn, :webfinger, %{}) + end +end diff --git a/lib/postland_web/controllers/webfinger_json.ex b/lib/postland_web/controllers/webfinger_json.ex new file mode 100644 index 0000000..f6e7c6e --- /dev/null +++ b/lib/postland_web/controllers/webfinger_json.ex @@ -0,0 +1,21 @@ +defmodule PostlandWeb.WebfingerJSON do + use Phoenix.VerifiedRoutes, endpoint: PostlandWeb.Endpoint, router: PostlandWeb.Router + + alias Postland.Accounts + + def render("webfinger.json", _assigns) do + user = Accounts.solo_user() + + # TODO: Check that the host here is correct after deploy + %{ + subject: "acct:#{user.username}@#{PostlandWeb.Endpoint.host()}", + links: [ + %{ + "rel" => "self", + "type" => "application/activity+json", + "href" => url(~p"/actor") + } + ] + } + end +end diff --git a/lib/postland_web/router.ex b/lib/postland_web/router.ex index 1dfcd8b..2743bd4 100644 --- a/lib/postland_web/router.ex +++ b/lib/postland_web/router.ex @@ -21,6 +21,14 @@ defmodule PostlandWeb.Router do plug PostlandWeb.Plugs.RedirectIfNotSetup end + scope "/", PostlandWeb do + pipe_through [:api] + + get "/.well-known/webfinger", WebfingerController, :get + + get "/actor", ActorController, :get + end + scope "/", PostlandWeb do pipe_through [:browser, :redirect_if_not_set_up] @@ -55,9 +63,7 @@ defmodule PostlandWeb.Router do live_session :redirect_if_user_is_authenticated, on_mount: [{PostlandWeb.UserAuth, :redirect_if_user_is_authenticated}] do - live "/users/register", UserRegistrationLive, :new live "/users/log_in", UserLoginLive, :new - live "/users/reset_password/:token", UserResetPasswordLive, :edit end post "/users/log_in", UserSessionController, :create diff --git a/priv/repo/migrations/20240921235213_add_keys_to_user.exs b/priv/repo/migrations/20240921235213_add_keys_to_user.exs new file mode 100644 index 0000000..fc876a5 --- /dev/null +++ b/priv/repo/migrations/20240921235213_add_keys_to_user.exs @@ -0,0 +1,10 @@ +defmodule Postland.Repo.Migrations.AddKeysToUser do + use Ecto.Migration + + def change do + alter table("users") do + add :private_key, :text, null: false + add :public_key, :text, null: false + end + end +end