Merge branch 'fixes' into 'master'

Some fixes

See merge request framasoft/mobilizon!869
This commit is contained in:
Thomas Citharel 2021-03-25 14:25:01 +00:00
commit cd7732f6a5
35 changed files with 790 additions and 501 deletions

1
.gitignore vendored
View file

@ -14,6 +14,7 @@ erl_crash.dump
# secrets files as long as you replace their contents by environment
# variables.
/config/*.secret.exs
/config/runtime.exs
/setup_db.psql

View file

@ -8,5 +8,5 @@
out: "",
threshold: "medium",
ignore: ["Config.HTTPS", "Config.CSP"],
ignore_files: ["config/dev.1.secret.exs", "config/dev.2.secret.exs", "config/dev.3.secret.exs", "config/dev.secret.exs", "config/e2e.secret.exs", "config/prod.secret.exs", "config/test.secret.exs"]
ignore_files: ["config/dev.1.secret.exs", "config/dev.2.secret.exs", "config/dev.3.secret.exs", "config/dev.secret.exs", "config/e2e.secret.exs", "config/prod.secret.exs", "config/test.secret.exs", "config/runtime.1.secret.exs", "config/runtime.2.secret.exs", "config/runtime.3.secret.exs", "config/runtime.exs"]
]

View file

@ -2,3 +2,4 @@
5048AE33D6269B15E21CF28C6F545AB6
752C0E897CA81ACD81F4BB215FA5F8E4
23412CF16549E4E88366DC9DECF39071

View file

@ -59,7 +59,8 @@ config :mobilizon, Mobilizon.Web.Endpoint,
config :mime, :types, %{
"application/activity+json" => ["activity-json"],
"application/ld+json" => ["activity-json"],
"application/jrd+json" => ["jrd-json"]
"application/jrd+json" => ["jrd-json"],
"application/xrd+xml" => ["xrd-xml"]
}
# Upload configuration

View file

@ -97,20 +97,3 @@ config :mobilizon, :anonymous,
reports: [
allowed: true
]
require Logger
cond do
System.get_env("INSTANCE_CONFIG") &&
File.exists?("./config/#{System.get_env("INSTANCE_CONFIG")}") ->
import_config System.get_env("INSTANCE_CONFIG")
System.get_env("DOCKER", "false") == "false" && File.exists?("./config/dev.secret.exs") ->
import_config "dev.secret.exs"
System.get_env("DOCKER", "false") == "true" ->
Logger.info("Using environment configuration for Docker")
true ->
Logger.error("No configuration file found")
end

View file

@ -33,7 +33,7 @@
"graphql-tag": "^2.10.3",
"intersection-observer": "^0.12.0",
"leaflet": "^1.4.0",
"leaflet.locatecontrol": "^0.72.0",
"leaflet.locatecontrol": "^0.73.0",
"lodash": "^4.17.11",
"ngeohash": "^0.6.3",
"phoenix": "^1.4.11",
@ -67,14 +67,14 @@
"@types/vuedraggable": "^2.23.0",
"@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
"@vue/cli-plugin-babel": "~4.5.11",
"@vue/cli-plugin-e2e-cypress": "~4.5.11",
"@vue/cli-plugin-eslint": "~4.5.11",
"@vue/cli-plugin-pwa": "~4.5.11",
"@vue/cli-plugin-router": "~4.5.11",
"@vue/cli-plugin-typescript": "~4.5.11",
"@vue/cli-plugin-unit-jest": "~4.5.11",
"@vue/cli-service": "~4.5.11",
"@vue/cli-plugin-babel": "~4.5.12",
"@vue/cli-plugin-e2e-cypress": "~4.5.12",
"@vue/cli-plugin-eslint": "~4.5.12",
"@vue/cli-plugin-pwa": "~4.5.12",
"@vue/cli-plugin-router": "~4.5.12",
"@vue/cli-plugin-typescript": "~4.5.12",
"@vue/cli-plugin-unit-jest": "~4.5.12",
"@vue/cli-service": "~4.5.12",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^1.1.0",

View file

@ -24,6 +24,9 @@
v-model="newComment.text"
/>
</p>
<p class="help is-danger" v-if="emptyCommentError">
{{ $t("Comment text can't be empty") }}
</p>
</div>
<div class="send-comment">
<b-button
@ -125,12 +128,23 @@ export default class CommentTree extends Vue {
CommentModeration = CommentModeration;
emptyCommentError = false;
@Watch("currentActor")
watchCurrentActor(currentActor: IPerson): void {
this.newComment.actor = currentActor;
}
@Watch("newComment", { deep: true })
resetEmptyCommentError(newComment: IComment): void {
if (this.emptyCommentError) {
this.emptyCommentError = ["", "<p></p>"].includes(newComment.text);
}
}
async createCommentForEvent(comment: IComment): Promise<void> {
this.emptyCommentError = ["", "<p></p>"].includes(comment.text);
if (this.emptyCommentError) return;
try {
if (!comment.actor) return;
await this.$apollo.mutate({
@ -216,10 +230,13 @@ export default class CommentTree extends Vue {
// and reset the new comment field
this.newComment = new CommentModel();
} catch (error) {
console.error(error);
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);
} catch (errors) {
console.error(errors);
if (errors.graphQLErrors && errors.graphQLErrors.length > 0) {
const error = errors.graphQLErrors[0];
if (error.field !== "text" && error.message[0] !== "can't be blank") {
this.$notifier.error(error.message);
}
}
}
}

View file

@ -967,5 +967,7 @@
"You posted a comment on the event {event}.": "You posted a comment on the event {event}.",
"{profile} posted a comment on the event {event}.": "{profile} posted a comment on the event {event}.",
"You replied to a comment on the event {event}.": "You replied to a comment on the event {event}.",
"{profile} replied to a comment on the event {event}.": "{profile} replied to a comment on the event {event}."
"{profile} replied to a comment on the event {event}.": "{profile} replied to a comment on the event {event}.",
"New post": "New post",
"Comment text can't be empty": "Comment text can't be empty"
}

View file

@ -1061,5 +1061,7 @@
"You posted a comment on the event {event}.": "Vous avez posté un commentaire sur l'événement {event}.",
"{profile} posted a comment on the event {event}.": "{profile} a posté un commentaire sur l'événement {event}.",
"You replied to a comment on the event {event}.": "Vous avez répondu à un commentaire sur l'événement {event}.",
"{profile} replied to a comment on the event {event}.": "{profile} a répondu à un commentaire sur l'événement {event}."
"{profile} replied to a comment on the event {event}.": "{profile} a répondu à un commentaire sur l'événement {event}.",
"New post": "Nouveau billet",
"Comment text can't be empty": "Le texte du commentaire ne peut être vide"
}

View file

@ -34,7 +34,7 @@ import { Component, Vue } from "vue-property-decorator";
variables() {
return {
id: this.currentActor.id,
group: this.$route.params.preferredUsername,
group: this.group.preferredUsername,
};
},
subscribeToMore: {
@ -42,14 +42,14 @@ import { Component, Vue } from "vue-property-decorator";
variables() {
return {
actorId: this.currentActor.id,
group: this.$route.params.preferredUsername,
group: this.group.preferredUsername,
};
},
skip() {
return (
!this.currentActor ||
!this.currentActor.id ||
!this.$route.params.preferredUsername
!this.group.preferredUsername
);
},
},
@ -57,7 +57,7 @@ import { Component, Vue } from "vue-property-decorator";
return (
!this.currentActor ||
!this.currentActor.id ||
!this.$route.params.preferredUsername
!this.group.preferredUsername
);
},
},

View file

@ -3,7 +3,11 @@
<h1>{{ $t("Create a discussion") }}</h1>
<form @submit.prevent="createDiscussion">
<b-field :label="$t('Title')">
<b-field
:label="$t('Title')"
:message="errors.title"
:type="errors.title ? 'is-danger' : undefined"
>
<b-input aria-required="true" required v-model="discussion.title" />
</b-field>
@ -64,7 +68,10 @@ export default class CreateDiscussion extends Vue {
discussion = { title: "", text: "" };
errors = { title: "" };
async createDiscussion(): Promise<void> {
this.errors = { title: "" };
try {
if (!this.group.id || !this.currentActor.id) return;
const { data } = await this.$apollo.mutate({
@ -86,7 +93,11 @@ export default class CreateDiscussion extends Vue {
} catch (error) {
console.error(error);
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);
if (error.graphQLErrors[0].field == "title") {
this.errors.title = error.graphQLErrors[0].message;
} else {
this.$notifier.error(error.graphQLErrors[0].message);
}
}
}
}

View file

@ -2,6 +2,43 @@
<div>
<form @submit.prevent="publish(false)" v-if="isCurrentActorAGroupModerator">
<div class="container section">
<nav class="breadcrumb" aria-label="breadcrumbs" v-if="actualGroup">
<ul>
<li>
<router-link
v-if="actualGroup"
:to="{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(actualGroup),
},
}"
>{{
actualGroup.name || actualGroup.preferredUsername
}}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
<li>
<router-link
v-if="actualGroup"
:to="{
name: RouteName.POSTS,
params: {
preferredUsername: usernameWithDomain(actualGroup),
},
}"
>{{ $t("Posts") }}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
<li class="is-active">
<span v-if="preferredUsername">{{ $t("New post") }}</span>
<span v-else-if="slug">{{ $t("Edit post") }}</span>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
</ul>
</nav>
<h1 class="title" v-if="isUpdate === true">
{{ $t("Edit post") }}
</h1>
@ -111,7 +148,6 @@
<script lang="ts">
import { Component, Prop, Watch } from "vue-property-decorator";
import { mixins } from "vue-class-component";
import { FETCH_GROUP } from "@/graphql/group";
import {
buildFileFromIMedia,
buildFileVariable,
@ -135,11 +171,24 @@ import TagInput from "../../components/Event/TagInput.vue";
import RouteName from "../../router/name";
import Subtitle from "../../components/Utils/Subtitle.vue";
import PictureUpload from "../../components/PictureUpload.vue";
import { PERSON_MEMBERSHIP_GROUP } from "@/graphql/actor";
import { FETCH_GROUP } from "@/graphql/group";
@Component({
apollo: {
tags: TAGS,
config: CONFIG,
group: {
query: FETCH_GROUP,
variables() {
return {
name: this.preferredUsername,
};
},
skip() {
return !this.preferredUsername;
},
},
post: {
query: FETCH_POST,
fetchPolicy: "cache-and-network",
@ -152,15 +201,17 @@ import PictureUpload from "../../components/PictureUpload.vue";
return !this.slug;
},
},
group: {
query: FETCH_GROUP,
person: {
query: PERSON_MEMBERSHIP_GROUP,
fetchPolicy: "cache-and-network",
variables() {
return {
name: this.preferredUsername,
id: this.currentActor.id,
group: this.actualGroup.preferredUsername,
};
},
skip() {
return !this.preferredUsername;
return !this.currentActor?.id || !this.actualGroup?.preferredUsername;
},
},
},
@ -206,6 +257,10 @@ export default class EditPost extends mixins(GroupMixin) {
errors: Record<string, unknown> = {};
RouteName = RouteName;
usernameWithDomain = usernameWithDomain;
async mounted(): Promise<void> {
this.pictureFile = await buildFileFromIMedia(this.post.picture);
}
@ -331,18 +386,6 @@ export default class EditPost extends mixins(GroupMixin) {
}
return this.group;
}
hasCurrentActorThisRole(givenRole: string | string[]): boolean {
const roles = Array.isArray(givenRole) ? givenRole : [givenRole];
return (
this.person &&
this.actualGroup &&
this.person.memberships.elements.some(
({ parent: { id }, role }) =>
id === this.actualGroup.id && roles.includes(role)
)
);
}
}
</script>
<style lang="scss" scoped>
@ -360,4 +403,8 @@ form {
}
}
}
.breadcrumb li.is-active > span {
padding: 0 0.75em;
}
</style>

View file

@ -190,6 +190,9 @@
<b-modal :active.sync="createLinkResourceModal" has-modal-card>
<div class="modal-card">
<section class="modal-card-body">
<b-message type="is-danger" v-if="modalError">
{{ modalError }}
</b-message>
<form @submit.prevent="createResource">
<b-field :label="$t('URL')">
<b-input
@ -317,6 +320,8 @@ export default class Resources extends Mixins(ResourceMixin) {
renameModal = false;
modalError = "";
groupObject: Record<string, unknown> = {
name: "resources",
pull: "clone",
@ -341,6 +346,7 @@ export default class Resources extends Mixins(ResourceMixin) {
async createResource(): Promise<void> {
if (!this.resource.actor) return;
this.modalError = "";
try {
await this.$apollo.mutate({
mutation: CREATE_RESOURCE,
@ -364,21 +370,28 @@ export default class Resources extends Mixins(ResourceMixin) {
this.newResource.resourceUrl = "";
} catch (err) {
console.error(err);
this.modalError = err.graphQLErrors[0].message;
}
}
async previewResource(): Promise<void> {
if (this.newResource.resourceUrl === "") return;
const { data } = await this.$apollo.mutate({
mutation: PREVIEW_RESOURCE_LINK,
variables: {
resourceUrl: this.newResource.resourceUrl,
},
});
this.newResource.title = data.previewResourceLink.title;
this.newResource.summary = data.previewResourceLink.description;
this.newResource.metadata = data.previewResourceLink;
this.newResource.type = "link";
this.modalError = "";
try {
if (this.newResource.resourceUrl === "") return;
const { data } = await this.$apollo.mutate({
mutation: PREVIEW_RESOURCE_LINK,
variables: {
resourceUrl: this.newResource.resourceUrl,
},
});
this.newResource.title = data.previewResourceLink.title;
this.newResource.summary = data.previewResourceLink.description;
this.newResource.metadata = data.previewResourceLink;
this.newResource.type = "link";
} catch (err) {
console.error(err);
this.modalError = err.graphQLErrors[0].message;
}
}
createSentenceForType(type: string): string {

View file

@ -47,6 +47,7 @@ export default class Validate extends Vue {
this.loading = false;
await this.$router.push({ name: RouteName.HOME });
} catch (err) {
this.loading = false;
console.error(err);
this.failed = true;
}

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
alias Mobilizon.Federation.ActivityPub.Audience
alias Mobilizon.Federation.ActivityPub.Types.Entity
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
alias Mobilizon.Service.Activity.Discussion, as: DiscussionActivity
alias Mobilizon.Web.Endpoint
import Mobilizon.Federation.ActivityPub.Utils, only: [make_create_data: 2, make_update_data: 2]
@ -17,7 +18,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
@impl Entity
@spec create(map(), map()) :: {:ok, map()}
def create(%{discussion_id: discussion_id} = args, additional) when not is_nil(discussion_id) do
with %Discussion{} = discussion <- Discussions.get_discussion(discussion_id),
with args <- prepare_args(args),
%Discussion{} = discussion <- Discussions.get_discussion(discussion_id),
{:ok, %Discussion{last_comment_id: last_comment_id} = discussion} <-
Discussions.reply_to_discussion(discussion, args),
{:ok, _} <-
@ -39,7 +41,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
@impl Entity
@spec create(map(), map()) :: {:ok, map()}
def create(args, additional) do
with {:ok, %Discussion{} = discussion} <-
with args <- prepare_args(args),
{:ok, %Discussion{} = discussion} <-
Discussions.create_discussion(args),
{:ok, _} <-
DiscussionActivity.insert_activity(discussion, subject: "discussion_created"),
@ -118,4 +121,18 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
:ok
end
defp prepare_args(args) do
{text, _mentions, _tags} =
APIUtils.make_content_html(
args |> Map.get(:text, "") |> String.trim(),
# Can't put additional tags on a comment
[],
"text/html"
)
args
|> Map.update(:title, "", &String.trim/1)
|> Map.put(:text, text)
end
end

View file

@ -42,6 +42,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
Comments.create_comment(args) do
{:ok, comment}
else
{:error, err} ->
{:error, err}
{:allowed, false} ->
{:error, :unauthorized}
end

View file

@ -104,6 +104,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
}) do
{:ok, discussion}
else
{:error, :discussion, err, _} ->
{:error, err}
{:member, false} ->
{:error, :unauthorized}
end

View file

@ -124,6 +124,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, %Actor{} = new_person} <- Actors.new_person(args) do
{:ok, new_person}
else
{:error, err} ->
{:error, err}
{:picture, {:error, :file_too_large}} ->
{:error, dgettext("errors", "The provided picture is too heavy")}
end
@ -232,7 +235,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
This function is used to register a person afterwards the user has been created (but not activated)
"""
def register_person(_parent, args, _resolution) do
with {:ok, %User{} = user} <- Users.get_user_by_email(args.email),
# When registering, email is assumed confirmed (unlike changing email)
with {:ok, %User{} = user} <- Users.get_user_by_email(args.email, unconfirmed: false),
user_actor <- Users.get_actor_for_user(user),
no_actor <- is_nil(user_actor),
{:no_actor, true} <- {:no_actor, no_actor},
@ -364,9 +368,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
context: %{current_user: %User{id: user_id, role: role}}
}
) do
with true <- actor_user_id == user_id or is_moderator(role),
with {:can_get_events, true} <-
{:can_get_events, actor_user_id == user_id or is_moderator(role)},
%Page{} = page <- Events.list_organized_events_for_actor(actor, page, limit) do
{:ok, page}
else
{:can_get_events, false} ->
{:error, :unauthorized}
end
end

View file

@ -118,6 +118,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
) do
{:ok, resource}
else
{:error, _} ->
{:error, dgettext("errors", "Error while creating resource")}
{:own_check, _} ->
{:error, dgettext("errors", "Parent resource doesn't belong to this group")}
@ -201,8 +204,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
{:ok, data} when is_map(data) ->
{:ok, struct(Metadata, data)}
{:error, _err} ->
{:error, :invalid_parsed_data} ->
{:error, dgettext("errors", "Unable to fetch resource details from this URL.")}
{:error, err} ->
Logger.warn("Error while fetching preview from #{inspect(resource_url)}")
Logger.debug(inspect(err))
{:error, :unknown_resource}
end
end

View file

@ -7,7 +7,6 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
alias Mobilizon.{Actors, Admin, Config, Events, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Crypto
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Service.Auth.Authenticator
@ -19,8 +18,6 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
require Logger
@confirmation_token_length 30
@doc """
Find an user by its ID
"""
@ -183,11 +180,11 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
@doc """
Send the confirmation email again.
We only do this to accounts unconfirmed
We only do this to accounts not activated
"""
def resend_confirmation_email(_parent, args, _resolution) do
with {:ok, %User{locale: locale} = user} <-
Users.get_user_by_email(Map.get(args, :email), false),
Users.get_user_by_email(Map.get(args, :email), activated: false, unconfirmed: false),
{:ok, email} <-
Email.User.resend_confirmation_email(user, Map.get(args, :locale, locale)) do
{:ok, email}
@ -205,7 +202,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
"""
def send_reset_password(_parent, args, _resolution) do
with email <- Map.get(args, :email),
{:ok, %User{locale: locale} = user} <- Users.get_user_by_email(email, true),
{:ok, %User{locale: locale} = user} <-
Users.get_user_by_email(email, activated: true, unconfirmed: false),
{:can_reset_password, true} <-
{:can_reset_password, Authenticator.can_reset_password?(user)},
{:ok, %Bamboo.Email{} = _email_html} <-
@ -256,6 +254,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
end
end
def change_default_actor(_parent, _args, _resolution), do: {:error, :unauthenticated}
@doc """
Returns the list of events for all of this user's identities are going to
"""
@ -355,14 +355,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:current_password, Authenticator.login(user.email, password)},
{:same_email, false} <- {:same_email, new_email == old_email},
{:email_valid, true} <- {:email_valid, Email.Checker.valid?(new_email)},
{:ok, %User{} = user} <-
user
|> User.changeset(%{
unconfirmed_email: new_email,
confirmation_token: Crypto.random_string(@confirmation_token_length),
confirmation_sent_at: DateTime.utc_now() |> DateTime.truncate(:second)
})
|> Repo.update() do
{:ok, %User{} = user} <- Users.update_user_email(user, new_email) do
user
|> Email.User.send_email_reset_old_email()
|> Email.Mailer.deliver_later()
@ -389,17 +382,12 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
end
def validate_email(_parent, %{token: token}, _resolution) do
with %User{} = user <- Users.get_user_by_activation_token(token),
{:ok, %User{} = user} <-
user
|> User.changeset(%{
email: user.unconfirmed_email,
unconfirmed_email: nil,
confirmation_token: nil,
confirmation_sent_at: nil
})
|> Repo.update() do
with {:get, %User{} = user} <- {:get, Users.get_user_by_activation_token(token)},
{:ok, %User{} = user} <- Users.validate_email(user) do
{:ok, user}
else
{:get, nil} ->
{:error, dgettext("errors", "Invalid activation token")}
end
end

View file

@ -16,7 +16,7 @@ defmodule Mix.Tasks.Mobilizon.CreateBot do
def run([email, name, summary, type, url]) do
start_mobilizon()
with {:ok, %User{} = user} <- Users.get_user_by_email(email, true),
with {:ok, %User{} = user} <- Users.get_user_by_email(email, activated: true),
actor <- Actors.register_bot(%{name: name, summary: summary}),
{:ok, %Bot{} = bot} <-
Actors.create_bot(%{

View file

@ -28,6 +28,7 @@ defmodule Mobilizon.Discussions.Discussion do
alias Mobilizon.Discussions.Discussion.TitleSlug
alias Mobilizon.Web.Endpoint
alias Mobilizon.Web.Router.Helpers, as: Routes
import Mobilizon.Web.Gettext, only: [dgettext: 2]
@type t :: %__MODULE__{
creator: Actor.t(),
@ -63,7 +64,7 @@ defmodule Mobilizon.Discussions.Discussion do
discussion
|> cast(attrs, @attrs)
|> maybe_generate_id()
|> validate_required([:title, :id])
|> validate_required([:title, :id], message: dgettext("errors", "can't be blank"))
|> TitleSlug.maybe_generate_slug()
|> TitleSlug.unique_constraint()
|> maybe_generate_url()

View file

@ -138,11 +138,12 @@ defmodule Mobilizon.Posts.Post do
defp process_tag(tag), do: Tag.changeset(%Tag{}, tag)
defp maybe_put_publish_date(%Changeset{} = changeset) do
publish_at =
default_publish_at =
if get_field(changeset, :draft, true) == false,
do: DateTime.utc_now() |> DateTime.truncate(:second),
else: nil
publish_at = get_change(changeset, :publish_at, default_publish_at)
put_change(changeset, :publish_at, publish_at)
end

View file

@ -10,7 +10,7 @@ defmodule Mobilizon.Users do
alias Ecto.Multi
alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.{Crypto, Events}
alias Mobilizon.Events.FeedToken
alias Mobilizon.Storage.{Page, Repo}
alias Mobilizon.Users.{Setting, User}
@ -19,6 +19,8 @@ defmodule Mobilizon.Users do
defenum(NotificationPendingNotificationDelay, none: 0, direct: 1, one_hour: 5, one_day: 10)
@confirmation_token_length 30
@doc """
Registers an user.
"""
@ -66,10 +68,12 @@ defmodule Mobilizon.Users do
@doc """
Gets an user by its email.
"""
@spec get_user_by_email(String.t(), boolean | nil) ::
@spec get_user_by_email(String.t(), Keyword.t()) ::
{:ok, User.t()} | {:error, :user_not_found}
def get_user_by_email(email, activated \\ nil) do
query = user_by_email_query(email, activated)
def get_user_by_email(email, options \\ []) do
activated = Keyword.get(options, :activated, nil)
unconfirmed = Keyword.get(options, :unconfirmed, true)
query = user_by_email_query(email, activated, unconfirmed)
case Repo.one(query) do
nil ->
@ -83,10 +87,13 @@ defmodule Mobilizon.Users do
@doc """
Gets an user by its email.
"""
@spec get_user_by_email!(String.t(), boolean | nil) :: User.t()
def get_user_by_email!(email, activated \\ nil) do
@spec get_user_by_email!(String.t(), Keyword.t()) :: User.t()
def get_user_by_email!(email, options \\ []) do
activated = Keyword.get(options, :activated, nil)
unconfirmed = Keyword.get(options, :unconfirmed, true)
email
|> user_by_email_query(activated)
|> user_by_email_query(activated, unconfirmed)
|> Repo.one!()
end
@ -123,6 +130,29 @@ defmodule Mobilizon.Users do
end
end
@spec update_user_email(User.t(), String.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def update_user_email(%User{} = user, new_email) do
user
|> User.changeset(%{
unconfirmed_email: new_email,
confirmation_token: Crypto.random_string(@confirmation_token_length),
confirmation_sent_at: DateTime.utc_now() |> DateTime.truncate(:second)
})
|> Repo.update()
end
@spec validate_email(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def validate_email(%User{} = user) do
user
|> User.changeset(%{
email: user.unconfirmed_email,
unconfirmed_email: nil,
confirmation_token: nil,
confirmation_sent_at: nil
})
|> Repo.update()
end
@delete_user_default_options [reserve_email: true]
@doc """
@ -375,29 +405,26 @@ defmodule Mobilizon.Users do
Setting.changeset(setting, %{})
end
@spec user_by_email_query(String.t(), boolean | nil) :: Ecto.Query.t()
defp user_by_email_query(email, nil) do
from(u in User,
where: u.email == ^email or u.unconfirmed_email == ^email,
preload: :default_actor
)
@spec user_by_email_query(String.t(), boolean | nil, boolean()) :: Ecto.Query.t()
defp user_by_email_query(email, activated, unconfirmed) do
User
|> where([u], u.email == ^email)
|> include_unconfirmed(unconfirmed, email)
|> filter_activated(activated)
|> preload([:default_actor])
end
defp user_by_email_query(email, true) do
from(
u in User,
where: u.email == ^email and not is_nil(u.confirmed_at) and not u.disabled,
preload: :default_actor
)
end
defp include_unconfirmed(query, false, _email), do: query
defp user_by_email_query(email, false) do
from(
u in User,
where: (u.email == ^email or u.unconfirmed_email == ^email) and is_nil(u.confirmed_at),
preload: :default_actor
)
end
defp include_unconfirmed(query, true, email),
do: or_where(query, [u], u.unconfirmed_email == ^email)
defp filter_activated(query, nil), do: query
defp filter_activated(query, true),
do: where(query, [u], not is_nil(u.confirmed_at) and not u.disabled)
defp filter_activated(query, false), do: where(query, [u], is_nil(u.confirmed_at))
@spec user_by_activation_token_query(String.t()) :: Ecto.Query.t()
defp user_by_activation_token_query(token) do

View file

@ -86,7 +86,7 @@ defmodule Mobilizon.Service.Auth.Authenticator do
def fetch_user(nil), do: {:error, :user_not_found}
def fetch_user(email) when not is_nil(email) do
with {:ok, %User{} = user} <- Users.get_user_by_email(email, true) do
with {:ok, %User{} = user} <- Users.get_user_by_email(email, activated: true) do
user
end
end

View file

@ -57,7 +57,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
@spec parse_url(String.t(), Enum.t()) :: {:ok, map()} | {:error, any()}
defp parse_url(url, options \\ []) do
user_agent = Keyword.get(options, :user_agent, Config.instance_user_agent())
user_agent = Keyword.get(options, :user_agent, default_user_agent(url))
headers = [{"User-Agent", user_agent}]
Logger.debug("Fetching content at address #{inspect(url)}")
@ -212,7 +212,8 @@ defmodule Mobilizon.Service.RichMedia.Parser do
end
defp check_parsed_data(data) do
{:error, "Found metadata was invalid or incomplete: #{inspect(data)}"}
Logger.debug("Found metadata was invalid or incomplete: #{inspect(data)}")
{:error, :invalid_parsed_data}
end
defp clean_parsed_data(data) do
@ -293,4 +294,17 @@ defmodule Mobilizon.Service.RichMedia.Parser do
end
defp check_remote_picture_path(data), do: data
# Twitter requires a well-know crawler user-agent to show server-rendered data
defp default_user_agent("https://twitter.com/" <> _) do
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
end
defp default_user_agent("https://mobile.twitter.com/" <> _) do
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
end
defp default_user_agent(_url) do
Config.instance_user_agent()
end
end

View file

@ -70,8 +70,14 @@ defmodule Mobilizon.Service.RichMedia.Parsers.MetaTagsParser do
defp maybe_put_description(meta, html) when meta != %{} do
case get_page_description(html) do
"" -> meta
description -> Map.put_new(meta, :description, description)
"" ->
meta
descriptions when is_list(descriptions) and length(descriptions) > 0 ->
Map.put_new(meta, :description, hd(descriptions))
description ->
Map.put_new(meta, :description, description)
end
end

View file

@ -120,7 +120,8 @@ defmodule Mobilizon.Web.ReverseProxy do
opts
end
with {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
with {:is_url, true} <- {:is_url, valid_uri?(url)},
{:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
:ok <- header_length_constraint(headers, Keyword.get(opts, :max_body_length)) do
response(conn, client, url, code, headers, opts)
else
@ -129,6 +130,13 @@ defmodule Mobilizon.Web.ReverseProxy do
|> head_response(url, code, headers, opts)
|> halt()
{:is_url, false} ->
Logger.warn("Tried to reverse proxy URL #{inspect(url)}")
conn
|> error_or_redirect(url, 500, "Request failed", opts)
|> halt()
{:error, {:invalid_http_response, code}} ->
Logger.error("#{__MODULE__}: request to #{inspect(url)} failed with HTTP status #{code}")
@ -397,4 +405,10 @@ defmodule Mobilizon.Web.ReverseProxy do
def filename(url_or_path) do
if path = URI.parse(url_or_path).path, do: Path.basename(path)
end
@spec valid_uri?(String.t()) :: boolean()
defp valid_uri?(url) do
uri = URI.parse(url)
uri.scheme != nil && uri.host =~ "."
end
end

View file

@ -9,6 +9,10 @@ defmodule Mobilizon.Web.Router do
plug(Mobilizon.Web.Auth.Pipeline)
end
pipeline :host_meta do
plug(:accepts, ["xrd-xml"])
end
pipeline :well_known do
plug(:accepts, ["json", "jrd-json"])
end
@ -67,9 +71,14 @@ defmodule Mobilizon.Web.Router do
end
scope "/.well-known", Mobilizon.Web do
pipe_through(:well_known)
pipe_through(:host_meta)
get("/host-meta", WebFingerController, :host_meta)
end
scope "/.well-known", Mobilizon.Web do
pipe_through(:well_known)
get("/webfinger", WebFingerController, :webfinger)
get("/nodeinfo", NodeInfoController, :schemas)
get("/nodeinfo/:version", NodeInfoController, :nodeinfo)

View file

@ -138,7 +138,7 @@ defmodule Mobilizon.Mixfile do
{:tesla, "~> 1.4.0"},
{:sitemapper, "~> 0.5.0"},
{:xml_builder, "~> 2.1.1"},
{:remote_ip, "~> 0.2.0"},
{:remote_ip, "~> 1.0.0"},
{:ex_cldr_languages, "~> 0.2.1"},
{:slugger, "~> 0.3"},
{:sentry, "~> 8.0"},

View file

@ -9,8 +9,8 @@
"bamboo_smtp": {:hex, :bamboo_smtp, "4.0.0", "0cc7df161d5d440d280a6d2eb20bf80bc45ea77161728a229e5ab339dcd087cd", [:mix], [{:bamboo, "~> 2.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 1.1.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "2412015092121b9f24f3f2e654bcd98e5c5f9afb323a94f8defa22e70ba8f23d"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"cachex": {:hex, :cachex, "3.3.0", "6f2ebb8f27491fe39121bd207c78badc499214d76c695658b19d6079beeca5c2", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d90e5ee1dde14cef33f6b187af4335b88748b72b30c038969176cd4e6ccc31a1"},
"certifi": {:hex, :certifi, "2.5.3", "70bdd7e7188c804f3a30ee0e7c99655bc35d8ac41c23e12325f36ab449b70651", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "ed516acb3929b101208a9d700062d520f3953da3b6b918d866106ffa980e1c10"},
"cldr_utils": {:hex, :cldr_utils, "2.15.0", "69a759a73376587aadde3657cd05e86324322dece89508599879b803dd8fa5c5", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "d5632a974e82bacdcb636fafc6b4660ff901f8818ffa058511584de6bfb0df2c"},
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
"cldr_utils": {:hex, :cldr_utils, "2.15.1", "1136987437ac4821f3ec7d46ff150f1c020a1f79730ff1252bfa7681ed577f31", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "ca3c8da0aab18b4d944541392e9548422e30854e3a9d7768dc629c8123f8efb8"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
@ -41,7 +41,7 @@
"ex_cldr_languages": {:hex, :ex_cldr_languages, "0.2.2", "d7dab93272443b70e18e6aef0f62974418baaca3a3b31a66d51921ec1547113c", [:mix], [{:ex_cldr, "~> 2.2 and >= 2.2.1", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "d9cbf4bf643365b0042e774520ddfcbc31618efd5a5383fac98f75149961622c"},
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.16.1", "aea1cecbf19dfb6ac82658d05685015172866cf1a4ce0778f58157442ccf015d", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.18", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.8", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ba7a6f9f50aaa7759d05e508a333747cf670c823b2857780b025f3c421f4f1d3"},
"ex_crypto": {:hex, :ex_crypto, "0.10.0", "af600a89b784b36613a989da6e998c1b200ff1214c3cfbaf8deca4aa2f0a1739", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "ccc7472cfe8a0f4565f97dce7e9280119bf15a5ea51c6535e5b65f00660cde1c"},
"ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"},
"ex_doc": {:hex, :ex_doc, "0.24.1", "15673de99154f93ca7f05900e4e4155ced1ee0cd34e0caeee567900a616871a4", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "07972f17bdf7dc7b5bd76ec97b556b26178ed3f056e7ec9288eb7cea7f91cce2"},
"ex_ical": {:hex, :ex_ical, "0.2.0", "4b928b554614704016cc0c9ee226eb854da9327a1cc460457621ceacb1ac29a6", [:mix], [{:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "db76473b2ae0259e6633c6c479a5a4d8603f09497f55c88f9ef4d53d2b75befb"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_optimizer": {:hex, :ex_optimizer, "0.1.1", "62da37e206fc2233ff7a4e54e40eae365c40f96c81992fcd15b782eb25169b80", [:mix], [{:file_info, "~> 0.0.4", [hex: :file_info, repo: "hexpm", optional: false]}], "hexpm", "e6f5c059bcd58b66be2f6f257fdc4f69b74b0fa5c9ddd669486af012e4b52286"},
@ -66,7 +66,7 @@
"guardian": {:hex, :guardian, "2.1.1", "1f02b349f6ba765647cc834036a8d76fa4bd65605342fe3a031df3c99d0d411a", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "189b87ba7ce6b40d6ba029138098b96ffc4ae78f229f5b39539b9141af8bf0f8"},
"guardian_db": {:hex, :guardian_db, "2.1.0", "ec95a9d99cdd1e550555d09a7bb4a340d8887aad0697f594590c2fd74be02426", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f8e7d543ac92c395f3a7fd5acbe6829faeade57d688f7562e2f0fca8f94a0d70"},
"guardian_phoenix": {:hex, :guardian_phoenix, "2.0.1", "89a817265af09a6ddf7cb1e77f17ffca90cea2db10ff888375ef34502b2731b1", [:mix], [{:guardian, "~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "21f439246715192b231f228680465d1ed5fbdf01555a4a3b17165532f5f9a08c"},
"hackney": {:hex, :hackney, "1.17.0", "717ea195fd2f898d9fe9f1ce0afcc2621a41ecfe137fae57e7fe6e9484b9aa99", [:rebar3], [{:certifi, "~>2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "64c22225f1ea8855f584720c0e5b3cd14095703af1c9fbc845ba042811dc671c"},
"hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2d4b1c2579d85534035f12c9e1260abdf6d03a9ad4f515b2ee53b50e68c8b787"},
"http_signatures": {:hex, :http_signatures, "0.1.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"},
@ -84,6 +84,7 @@
"linkify": {:hex, :linkify, "0.5.0", "e0ea8de73ff44742d6a889721221f4c4eccaad5284957ee9832ffeb347602d54", [:mix], [], "hexpm", "4ccd958350aee7c51c89e21f05b15d30596ebbba707e051d21766be1809df2d7"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"},
@ -107,23 +108,23 @@
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"},
"plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"},
"plug_crypto": {:hex, :plug_crypto, "1.2.1", "5c854427528bf61d159855cedddffc0625e2228b5f30eff76d5a4de42d896ef4", [:mix], [], "hexpm", "6961c0e17febd9d0bfa89632d391d2545d2e0eb73768f5f50305a23961d8782c"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
"postgrex": {:hex, :postgrex, "0.15.8", "f5e782bbe5e8fa178d5e3cd1999c857dc48eda95f0a4d7f7bd92a50e84a0d491", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "698fbfacea34c4cf22c8281abeb5cf68d99628d541874f085520ab3b53d356fe"},
"progress_bar": {:hex, :progress_bar, "2.0.1", "7b40200112ae533d5adceb80ff75fbe66dc753bca5f6c55c073bfc122d71896d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "2519eb58a2f149a3a094e729378256d8cb6d96a259ec94841bd69fdc71f18f87"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"remote_ip": {:hex, :remote_ip, "0.2.1", "cd27cd8ea54ecaaf3532776ff4c5e353b3804e710302e88c01eadeaaf42e7e24", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:inet_cidr, "~> 1.0", [hex: :inet_cidr, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2e7ab1a461cc3cd5719f37e116a08f45c8b8493923063631b164315d6b7ee8e0"},
"remote_ip": {:hex, :remote_ip, "1.0.0", "3d7fb45204a5704443f480cee9515e464997f52c35e0a60b6ece1f81484067ae", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9e9fcad4e50c43b5234bb6a9629ed6ab223f3ed07147bd35470e4ee5c8caf907"},
"rsa_ex": {:hex, :rsa_ex, "0.4.0", "e28dd7dc5236e156df434af0e4aa822384c8866c928e17b785d4edb7c253b558", [:mix], [], "hexpm", "40e1f08e8401da4be59a6dd0f4da30c42d5bb01703161f0208d839d97db27f4e"},
"sentry": {:hex, :sentry, "8.0.5", "5ca922b9238a50c7258b52f47364b2d545beda5e436c7a43965b34577f1ef61f", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "4972839fdbf52e886d7b3e694c8adf421f764f2fa79036b88fb4742049bd4b7c"},
"shortuuid": {:hex, :shortuuid, "2.1.2", "14dbafdb2f6c7213fdfcc05c7572384b5051a7b1621170018ad4c05504bd96c1", [:mix], [], "hexpm", "d9b0c4f37500ea5199b6275ece872e213e9f45a015caf4aa777cec84f63ad353"},
"sitemapper": {:hex, :sitemapper, "0.5.0", "23b0bb7b3888f03d4e4e5bedb7034e6d2979e169366372d960d6f433112b9bdf", [:mix], [{:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:xml_builder, "~> 2.1.1", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "be7acff8d0245aa7ca125b9c4d0751009bbbca26ef866d888fef4fdf98670e41"},
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
"sobelow": {:hex, :sobelow, "0.11.0", "cdc17e3a9f1ea78dc55dbe0a03121cb6767fef737c6d9f1e62ee7e78730abccc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c57807bfe6f231338b657781f89ef0320b66a0dbe779aa911d6ed27cfa14ae6e"},
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"},
"timex": {:hex, :timex, "3.6.4", "137a49450b8d1f80efff82de4b78ab9ad2e367f06346825704310599733f338b", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f6fce3f07ab67f525043af5b1f68ed5fa12a41b9dab95a9a98bb6acfb30ecadc"},
"timex": {:hex, :timex, "3.7.3", "df8a2ea814749d700d6878ab9eacac9fdb498ecee2f507cb0002ec172bc24d0f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8691c1d86ca3a7bc14a156e2199dc8927be95d1a8f0e3b69e4bb2d6262c53ac6"},
"tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
"ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
"ueberauth_discord": {:hex, :ueberauth_discord, "0.5.2", "afc5d68879575c365972fd4d7cf7b01c16f7d062fc6bf7e86e2595736ac41127", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.3", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "bbd7701d8fef02623fb106ed7c24427ed2327ef769bcb1d2eba5670e54716cdc"},

File diff suppressed because one or more lines are too long

View file

@ -121,6 +121,16 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
}
"""
@change_default_actor """
mutation ChangeDefaultActor($preferredUsername: String!) {
changeDefaultActor(preferredUsername: $preferredUsername) {
defaultActor {
preferredUsername
}
}
}
"""
@valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"}
@valid_single_actor_params %{preferred_username: "test2", keys: "yolo"}
@ -835,7 +845,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
end
describe "Resolver: change default actor for user" do
test "test change_default_actor/3 with valid actor", context do
test "test change_default_actor/3 without being logged-in", %{conn: conn} do
# Prepare user with two actors
user = insert(:user)
insert(:actor, user: user)
@ -848,24 +858,41 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
assert {:ok, %User{actors: actors}} = Users.get_user_with_actors(user.id)
assert length(actors) == 2
mutation = """
mutation {
changeDefaultActor(preferred_username: "#{actor2.preferred_username}") {
default_actor {
preferred_username
}
}
}
"""
res =
conn
|> AbsintheHelpers.graphql_query(
query: @change_default_actor,
variables: %{preferredUsername: actor2.preferred_username}
)
assert hd(res["errors"])["message"] == "You need to be logged in"
end
test "test change_default_actor/3 with valid actor", %{conn: conn} do
# Prepare user with two actors
user = insert(:user)
insert(:actor, user: user)
assert {:ok, %User{actors: _actors}} = Users.get_user_with_actors(user.id)
actor_params = @valid_single_actor_params |> Map.put(:user_id, user.id)
assert {:ok, %Actor{} = actor2} = Actors.create_actor(actor_params)
assert {:ok, %User{actors: actors}} = Users.get_user_with_actors(user.id)
assert length(actors) == 2
res =
context.conn
conn
|> auth_conn(user)
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|> AbsintheHelpers.graphql_query(
query: @change_default_actor,
variables: %{preferredUsername: actor2.preferred_username}
)
assert json_response(res, 200)["data"]["changeDefaultActor"]["default_actor"][
"preferred_username"
] == actor2.preferred_username
assert res["errors"] == nil
assert res["data"]["changeDefaultActor"]["defaultActor"]["preferredUsername"] ==
actor2.preferred_username
end
end
@ -1113,6 +1140,59 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
assert user.unconfirmed_email == nil
end
test "change_email/3 with valid email but invalid token", %{conn: conn} do
{:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})
# Hammer time !
{:ok, %User{} = _user} =
Users.update_user(user, %{
confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
confirmation_sent_at: nil,
confirmation_token: nil
})
res =
conn
|> AbsintheHelpers.graphql_query(
query: @login_mutation,
variables: %{email: @old_email, password: @password}
)
login = res["data"]["login"]
assert Map.has_key?(login, "accessToken") && not is_nil(login["accessToken"])
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @change_email_mutation,
variables: %{email: @new_email, password: @password}
)
assert res["errors"] == nil
assert res["data"]["changeEmail"]["id"] == to_string(user.id)
user = Users.get_user!(user.id)
assert user.email == @old_email
assert user.unconfirmed_email == @new_email
assert_delivered_email(Email.User.send_email_reset_old_email(user))
assert_delivered_email(Email.User.send_email_reset_new_email(user))
res =
conn
|> AbsintheHelpers.graphql_query(
query: @validate_email_mutation,
variables: %{token: "some token"}
)
assert hd(res["errors"])["message"] == "Invalid activation token"
user = Users.get_user!(user.id)
assert user.email == @old_email
assert user.unconfirmed_email == @new_email
end
test "change_email/3 with invalid password", %{conn: conn} do
{:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})

View file

@ -85,9 +85,9 @@ defmodule Mobilizon.UsersTest do
test "get_user_by_email/1 finds an activated user by its email" do
{:ok, %User{} = user} = Users.register(%{email: @email, password: @password})
{:ok, %User{id: id}} = Users.get_user_by_email(@email, false)
{:ok, %User{id: id}} = Users.get_user_by_email(@email, activated: false)
assert id == user.id
assert {:error, :user_not_found} = Users.get_user_by_email(@email, true)
assert {:error, :user_not_found} = Users.get_user_by_email(@email, activated: true)
Users.update_user(user, %{
"confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second),
@ -95,10 +95,28 @@ defmodule Mobilizon.UsersTest do
"confirmation_token" => nil
})
assert {:error, :user_not_found} = Users.get_user_by_email(@email, false)
{:ok, %User{id: id}} = Users.get_user_by_email(@email, true)
assert {:error, :user_not_found} = Users.get_user_by_email(@email, activated: false)
{:ok, %User{id: id}} = Users.get_user_by_email(@email, activated: true)
assert id == user.id
end
@unconfirmed_email "unconfirmed@email.com"
test "get_user_by_email/1 finds an user by its pending email" do
{:ok, %User{} = user} = Users.register(%{email: @email, password: @password})
Users.update_user(user, %{
"confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second),
"confirmation_sent_at" => nil,
"confirmation_token" => nil
})
assert {:ok, %User{}} = Users.update_user_email(user, @unconfirmed_email)
assert {:error, :user_not_found} =
Users.get_user_by_email(@unconfirmed_email, unconfirmed: false)
assert {:ok, %User{}} = Users.get_user_by_email(@unconfirmed_email, unconfirmed: true)
end
end
describe "user_settings" do