Merge branch 'feature/invites' into 'develop'
Invites Closes #108 See merge request pleroma/pleroma!208
This commit is contained in:
commit
13440a80e1
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule Mix.Tasks.GenerateInviteToken do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@shortdoc "Generate invite token for user"
|
||||||
|
def run([]) do
|
||||||
|
Mix.Task.run("app.start")
|
||||||
|
|
||||||
|
with {:ok, token} <- Pleroma.UserInviteToken.create_token() do
|
||||||
|
IO.puts("Generated user invite token")
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
"Url: #{
|
||||||
|
Pleroma.Web.Router.Helpers.redirect_url(
|
||||||
|
Pleroma.Web.Endpoint,
|
||||||
|
:registration_page,
|
||||||
|
token.token
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
IO.puts("Error creating token")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,40 @@
|
||||||
|
defmodule Pleroma.UserInviteToken do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
alias Pleroma.{User, UserInviteToken, Repo}
|
||||||
|
|
||||||
|
schema "user_invite_tokens" do
|
||||||
|
field(:token, :string)
|
||||||
|
field(:used, :boolean, default: false)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_token do
|
||||||
|
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||||
|
|
||||||
|
token = %UserInviteToken{
|
||||||
|
used: false,
|
||||||
|
token: token
|
||||||
|
}
|
||||||
|
|
||||||
|
Repo.insert(token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def used_changeset(struct) do
|
||||||
|
struct
|
||||||
|
|> cast(%{}, [])
|
||||||
|
|> put_change(:used, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_used(token) do
|
||||||
|
with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}),
|
||||||
|
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||||
|
{:ok, token}
|
||||||
|
else
|
||||||
|
_e -> {:error, token}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -202,9 +202,7 @@ def user_fetcher(username) do
|
||||||
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
|
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
|
||||||
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
|
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
|
||||||
|
|
||||||
if @registrations_open do
|
post("/account/register", TwitterAPI.Controller, :register)
|
||||||
post("/account/register", TwitterAPI.Controller, :register)
|
|
||||||
end
|
|
||||||
|
|
||||||
get("/search", TwitterAPI.Controller, :search)
|
get("/search", TwitterAPI.Controller, :search)
|
||||||
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
|
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
|
||||||
|
@ -356,6 +354,7 @@ def user_fetcher(username) do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Fallback do
|
scope "/", Fallback do
|
||||||
|
get("/registration/:token", RedirectController, :registration_page)
|
||||||
get("/*path", RedirectController, :redirector)
|
get("/*path", RedirectController, :redirector)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -370,4 +369,8 @@ def redirector(conn, _params) do
|
||||||
|> send_file(200, "priv/static/index.html")
|
|> send_file(200, "priv/static/index.html")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def registration_page(conn, params) do
|
||||||
|
redirector(conn, params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||||
alias Pleroma.{User, Activity, Repo, Object}
|
alias Pleroma.{UserInviteToken, User, Activity, Repo, Object}
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.TwitterAPI.UserView
|
alias Pleroma.Web.TwitterAPI.UserView
|
||||||
alias Pleroma.Web.{OStatus, CommonAPI}
|
alias Pleroma.Web.{OStatus, CommonAPI}
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
@instance Application.get_env(:pleroma, :instance)
|
||||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||||
|
@registrations_open Keyword.get(@instance, :registrations_open)
|
||||||
|
|
||||||
def create_status(%User{} = user, %{"status" => _} = data) do
|
def create_status(%User{} = user, %{"status" => _} = data) do
|
||||||
CommonAPI.post(user, data)
|
CommonAPI.post(user, data)
|
||||||
|
@ -120,6 +122,8 @@ def upload(%Plug.Upload{} = file, format \\ "xml") do
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_user(params) do
|
def register_user(params) do
|
||||||
|
tokenString = params["token"]
|
||||||
|
|
||||||
params = %{
|
params = %{
|
||||||
nickname: params["nickname"],
|
nickname: params["nickname"],
|
||||||
name: params["fullname"],
|
name: params["fullname"],
|
||||||
|
@ -129,17 +133,32 @@ def register_user(params) do
|
||||||
password_confirmation: params["confirm"]
|
password_confirmation: params["confirm"]
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = User.register_changeset(%User{}, params)
|
# no need to query DB if registration is open
|
||||||
|
unless @registrations_open || is_nil(tokenString) do
|
||||||
|
token = Repo.get_by(UserInviteToken, %{token: tokenString})
|
||||||
|
end
|
||||||
|
|
||||||
with {:ok, user} <- Repo.insert(changeset) do
|
cond do
|
||||||
{:ok, user}
|
@registrations_open || (!is_nil(token) && !token.used) ->
|
||||||
else
|
changeset = User.register_changeset(%User{}, params)
|
||||||
{:error, changeset} ->
|
|
||||||
errors =
|
|
||||||
Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|
|
||||||
|> Jason.encode!()
|
|
||||||
|
|
||||||
{:error, %{error: errors}}
|
with {:ok, user} <- Repo.insert(changeset) do
|
||||||
|
!@registrations_open && UserInviteToken.mark_as_used(token.token)
|
||||||
|
{:ok, user}
|
||||||
|
else
|
||||||
|
{:error, changeset} ->
|
||||||
|
errors =
|
||||||
|
Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|
||||||
|
|> Jason.encode!()
|
||||||
|
|
||||||
|
{:error, %{error: errors}}
|
||||||
|
end
|
||||||
|
|
||||||
|
!@registrations_open && is_nil(token) ->
|
||||||
|
{:error, "Invalid token"}
|
||||||
|
|
||||||
|
!@registrations_open && token.used ->
|
||||||
|
{:error, "Expired token"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateUserInviteTokens do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:user_invite_tokens) do
|
||||||
|
add :token, :string
|
||||||
|
add :used, :boolean, default: false
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,7 +2,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
alias Pleroma.Builders.UserBuilder
|
alias Pleroma.Builders.UserBuilder
|
||||||
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
|
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
|
||||||
alias Pleroma.{Activity, User, Object, Repo}
|
alias Pleroma.{Activity, User, Object, Repo, UserInviteToken}
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.TwitterAPI.ActivityView
|
alias Pleroma.Web.TwitterAPI.ActivityView
|
||||||
|
|
||||||
|
@ -257,6 +257,70 @@ test "it registers a new user and returns the user." do
|
||||||
UserView.render("show.json", %{user: fetched_user})
|
UserView.render("show.json", %{user: fetched_user})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@moduletag skip: "needs 'registrations_open: false' in config"
|
||||||
|
test "it registers a new user via invite token and returns the user." do
|
||||||
|
{:ok, token} = UserInviteToken.create_token()
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
"nickname" => "vinny",
|
||||||
|
"email" => "pasta@pizza.vs",
|
||||||
|
"fullname" => "Vinny Vinesauce",
|
||||||
|
"bio" => "streamer",
|
||||||
|
"password" => "hiptofbees",
|
||||||
|
"confirm" => "hiptofbees",
|
||||||
|
"token" => token.token
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, user} = TwitterAPI.register_user(data)
|
||||||
|
|
||||||
|
fetched_user = Repo.get_by(User, nickname: "vinny")
|
||||||
|
token = Repo.get_by(UserInviteToken, token: token.token)
|
||||||
|
|
||||||
|
assert token.used == true
|
||||||
|
|
||||||
|
assert UserView.render("show.json", %{user: user}) ==
|
||||||
|
UserView.render("show.json", %{user: fetched_user})
|
||||||
|
end
|
||||||
|
|
||||||
|
@moduletag skip: "needs 'registrations_open: false' in config"
|
||||||
|
test "it returns an error if invalid token submitted" do
|
||||||
|
data = %{
|
||||||
|
"nickname" => "GrimReaper",
|
||||||
|
"email" => "death@reapers.afterlife",
|
||||||
|
"fullname" => "Reaper Grim",
|
||||||
|
"bio" => "Your time has come",
|
||||||
|
"password" => "scythe",
|
||||||
|
"confirm" => "scythe",
|
||||||
|
"token" => "DudeLetMeInImAFairy"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:error, msg} = TwitterAPI.register_user(data)
|
||||||
|
|
||||||
|
assert msg == "Invalid token"
|
||||||
|
refute Repo.get_by(User, nickname: "GrimReaper")
|
||||||
|
end
|
||||||
|
|
||||||
|
@moduletag skip: "needs 'registrations_open: false' in config"
|
||||||
|
test "it returns an error if expired token submitted" do
|
||||||
|
{:ok, token} = UserInviteToken.create_token()
|
||||||
|
UserInviteToken.mark_as_used(token.token)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
"nickname" => "GrimReaper",
|
||||||
|
"email" => "death@reapers.afterlife",
|
||||||
|
"fullname" => "Reaper Grim",
|
||||||
|
"bio" => "Your time has come",
|
||||||
|
"password" => "scythe",
|
||||||
|
"confirm" => "scythe",
|
||||||
|
"token" => token.token
|
||||||
|
}
|
||||||
|
|
||||||
|
{:error, msg} = TwitterAPI.register_user(data)
|
||||||
|
|
||||||
|
assert msg == "Expired token"
|
||||||
|
refute Repo.get_by(User, nickname: "GrimReaper")
|
||||||
|
end
|
||||||
|
|
||||||
test "it returns the error on registration problems" do
|
test "it returns the error on registration problems" do
|
||||||
data = %{
|
data = %{
|
||||||
"nickname" => "lain",
|
"nickname" => "lain",
|
||||||
|
|
Loading…
Reference in New Issue