Merge branch 'feature/invites' into 'develop'

Invites

Closes #108

See merge request pleroma/pleroma!208
This commit is contained in:
lambda 2018-08-12 12:04:15 +00:00
commit 13440a80e1
6 changed files with 177 additions and 14 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,9 +133,17 @@ def register_user(params) do
password_confirmation: params["confirm"] password_confirmation: params["confirm"]
} }
# no need to query DB if registration is open
unless @registrations_open || is_nil(tokenString) do
token = Repo.get_by(UserInviteToken, %{token: tokenString})
end
cond do
@registrations_open || (!is_nil(token) && !token.used) ->
changeset = User.register_changeset(%User{}, params) changeset = User.register_changeset(%User{}, params)
with {:ok, user} <- Repo.insert(changeset) do with {:ok, user} <- Repo.insert(changeset) do
!@registrations_open && UserInviteToken.mark_as_used(token.token)
{:ok, user} {:ok, user}
else else
{:error, changeset} -> {:error, changeset} ->
@ -141,6 +153,13 @@ def register_user(params) do
{:error, %{error: errors}} {:error, %{error: errors}}
end end
!@registrations_open && is_nil(token) ->
{:error, "Invalid token"}
!@registrations_open && token.used ->
{:error, "Expired token"}
end
end end
def get_by_id_or_nickname(id_or_nickname) do def get_by_id_or_nickname(id_or_nickname) do

View File

@ -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

View File

@ -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",