Merge branch 'fixes' into 'main'

Various fixes

Closes #1279, #900 et #822

See merge request framasoft/mobilizon!1399
This commit is contained in:
Thomas Citharel 2023-05-30 07:59:23 +00:00
commit 4643f00d9b
33 changed files with 1368 additions and 1028 deletions

4
.husky/pre-commit Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
cd js
yarn run lint-staged

View file

@ -14,7 +14,14 @@
"story:build": "histoire build",
"story:preview": "histoire preview",
"test": "vitest",
"coverage": "vitest run --coverage"
"coverage": "vitest run --coverage",
"prepare": "cd ../ && husky install"
},
"lint-staged": {
"**/*.{js,ts,vue}": [
"eslint --fix",
"prettier --write"
]
},
"dependencies": {
"@absinthe/socket": "^0.2.1",
@ -116,7 +123,9 @@
"eslint-plugin-vue": "^9.3.0",
"flush-promises": "^1.0.2",
"histoire": "^0.16.1",
"husky": "^8.0.3",
"jsdom": "^22.0.0",
"lint-staged": "^13.2.2",
"mock-apollo-client": "^1.1.0",
"prettier": "^2.2.1",
"prettier-eslint": "^15.0.1",
@ -124,7 +133,7 @@
"sass": "^1.34.1",
"typescript": "~5.0.0",
"vite": "^4.0.4",
"vite-plugin-pwa": "^0.14.1",
"vite-plugin-pwa": "^0.15.1",
"vitest": "^0.31.0",
"vue-i18n-extract": "^2.0.4"
}

View file

@ -42,13 +42,13 @@ body {
@apply bg-transparent text-black dark:text-white font-semibold py-2 px-4 border border-mbz-bluegreen dark:border-violet-3;
}
.btn-outlined-success {
@apply border-mbz-success;
@apply border-mbz-success hover:bg-mbz-success;
}
.btn-outlined-warning {
@apply bg-transparent border dark:text-white hover:dark:text-slate-900 hover:bg-mbz-warning border-mbz-warning;
}
.btn-outlined-danger {
@apply border-mbz-danger;
@apply border-mbz-danger hover:bg-mbz-danger;
}
.btn-outlined-text {
@apply bg-transparent hover:text-slate-900;

View file

@ -1,10 +1,10 @@
<template>
<router-link
class="flex gap-1 w-full items-center p-2 border-b-stone-200 border-b"
class="flex gap-1 w-full items-center p-2 border-b-stone-200 border-b bg-white dark:bg-transparent"
dir="auto"
:to="{
name: RouteName.DISCUSSION,
params: { slug: discussion.slug, id: discussion.id },
params: { slug: discussion.slug },
}"
>
<div class="">

View file

@ -157,7 +157,10 @@
</footer>
</form>
</o-collapse>
<div class="map" v-if="!hideMap">
<div
class="map"
v-if="!hideMap && !disabled && (selected.geom || detailsAddress)"
>
<map-leaflet
:coords="selected.geom ?? defaultCoords"
:marker="mapMarkerValue"

View file

@ -1,6 +1,6 @@
<template>
<group-section
:title="t('Public page')"
:title="t('Announcements')"
icon="bullhorn"
:privateSection="false"
:route="{

View file

@ -8,7 +8,10 @@
}"
>
<template #default>
<div v-if="group?.resources?.elements?.length ?? 0 > 0" class="p-1">
<div
v-if="group?.resources?.elements?.length ?? 0 > 0"
class="p-1 bg-white dark:bg-transparent"
>
<div
v-for="resource in group?.resources?.elements ?? []"
:key="resource.id"

View file

@ -87,7 +87,7 @@ import { useMutation } from "@vue/apollo-composable";
import { AUTORIZE_APPLICATION } from "@/graphql/application";
import RouteName from "@/router/name";
import { IApplication } from "@/types/application.model";
import { scope } from "./scopes";
import { scope as oAuthScopes } from "./scopes";
import AlertCircle from "vue-material-design-icons/AlertCircle.vue";
const { t } = useI18n({ useScope: "global" });
@ -104,7 +104,7 @@ const isOpen = ref<number>(-1);
const collapses = computed(() =>
(props.scope ?? "")
.split(" ")
.map((localScope) => scope[localScope])
.map((localScope) => oAuthScopes[localScope])
.filter((localScope) => localScope)
);

View file

@ -1,5 +1,5 @@
<template>
<div class="posts-wrapper">
<div class="posts-wrapper grid gap-4">
<post-list-item
v-for="post in posts"
:key="post.id"
@ -22,8 +22,6 @@ withDefaults(
</script>
<style lang="scss" scoped>
.posts-wrapper {
display: grid;
grid-gap: 20px;
grid-template: 1fr;
}
</style>

View file

@ -18,7 +18,7 @@
</h3>
<p class="flex gap-2">
<Clock />
<span dir="auto" class="" v-if="isBeforeLastWeek">{{
<span dir="auto" class="" v-if="publishedAt && isBeforeLastWeek">{{
formatDateTimeString(
publishedAt.toString(),
undefined,
@ -26,7 +26,7 @@
"short"
)
}}</span>
<span v-else>{{
<span v-else-if="publishedAt">{{
formatDistanceToNow(publishedAt, {
locale: dateFnsLocale,
addSuffix: true,
@ -47,6 +47,24 @@
</template>
</i18n-t>
</p>
<p
v-if="post.visibility === PostVisibility.UNLISTED"
class="flex gap-2 items-center"
>
<Link :size="16" />
{{ t("Accessible only by link") }}
</p>
<p
v-else-if="post.visibility === PostVisibility.PRIVATE"
class="flex gap-2 items-center"
>
<Lock :size="16" />
{{
t("Accessible only to members", {
group: post.attributedTo?.name,
})
}}
</p>
</div>
</router-link>
</template>
@ -61,7 +79,11 @@ import { formatDateTimeString } from "@/filters/datetime";
import Tag from "vue-material-design-icons/Tag.vue";
import AccountEdit from "vue-material-design-icons/AccountEdit.vue";
import Clock from "vue-material-design-icons/Clock.vue";
import Lock from "vue-material-design-icons/Lock.vue";
import Link from "vue-material-design-icons/Link.vue";
import MbzTag from "@/components/TagElement.vue";
import { PostVisibility } from "@/types/enums";
import { useI18n } from "vue-i18n";
const props = withDefaults(
defineProps<{
@ -71,34 +93,27 @@ const props = withDefaults(
{ isCurrentActorMember: false }
);
const { t } = useI18n({ useScope: "global" });
const dateFnsLocale = inject<Locale>("dateFnsLocale");
const postTags = computed(() => (props.post.tags ?? []).slice(0, 3));
const publishedAt = computed((): Date => {
return new Date((props.post.publishAt ?? props.post.insertedAt) as Date);
const publishedAt = computed((): Date | undefined => {
const date = props.post.publishAt ?? props.post.insertedAt;
if (!date) return undefined;
return new Date(date);
});
const isBeforeLastWeek = computed((): boolean => {
return isBefore(publishedAt.value, subWeeks(new Date(), 1));
return (
publishedAt.value !== undefined &&
isBefore(publishedAt.value, subWeeks(new Date(), 1))
);
});
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
// .post-minimalist-card-wrapper {
// display: grid;
// grid-gap: 5px 10px;
// grid-template-areas: "preview" "body";
// text-decoration: none;
// // width: 100%;
// // color: initial;
// // @include desktop {
// grid-template-columns: 200px 3fr;
// grid-template-areas: "preview body";
// // }
.title-info-wrapper {
.post-minimalist-title {
font-size: 18px;

View file

@ -7,7 +7,7 @@
>
<o-notification
variant="warning"
v-if="post.visibility !== PostVisibility.PUBLIC"
v-if="post.visibility === PostVisibility.UNLISTED"
:closable="false"
>
{{

View file

@ -1,5 +1,5 @@
<template>
<div class="resource-wrapper" dir="auto">
<div class="resource-wrapper bg-white dark:bg-transparent" dir="auto">
<router-link
:to="{
name: RouteName.RESOURCE_FOLDER,

View file

@ -1,7 +1,12 @@
<template>
<div class="flex flex-1 items-center w-full" dir="auto">
<div
class="flex flex-1 items-center w-full bg-white dark:bg-transparent"
dir="auto"
>
<a :href="resource.resourceUrl" target="_blank">
<div class="preview text-mbz-purple dark:text-mbz-purple-300">
<div
class="min-w-fit relative flex items-center justify-center text-mbz-purple dark:text-mbz-purple-300"
>
<div
v-if="
resource.type &&
@ -14,10 +19,12 @@
customSize="48"
/>
</div>
<div
class="preview-image"
<img
v-else-if="resource.metadata && resource.metadata.imageRemoteUrl"
:style="`background-image: url(${resource.metadata.imageRemoteUrl})`"
:src="resource.metadata.imageRemoteUrl"
alt=""
height="48"
width="48"
/>
<div class="preview-type" v-else>
<Link :size="48" />
@ -99,25 +106,6 @@ a {
overflow: hidden;
flex: 1;
.preview {
flex: 0 0 50px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
.preview-image {
border-radius: 4px 0 0 4px;
display: block;
margin: 0;
width: 100%;
height: 100%;
object-fit: cover;
background-size: cover;
background-position: 50%;
}
}
.body {
img.favicon {
display: inline-block;

View file

@ -34,7 +34,7 @@
import { computed, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
type position =
type positionValues =
| "top-right"
| "top"
| "top-left"
@ -49,7 +49,7 @@ const props = withDefaults(
onAction?: () => any;
cancelText?: string | null;
variant?: string;
position?: position;
position?: positionValues;
pauseOnHover?: boolean;
indefinite?: boolean;
}>(),

View file

@ -430,7 +430,6 @@
"Text": "Text",
"Upcoming events": "Upcoming events",
"Resources": "Resources",
"Public page": "Public page",
"Discussions": "Discussions",
"No public upcoming events": "No public upcoming events",
"Latest posts": "Latest posts",
@ -1564,5 +1563,6 @@
"This application will be allowed to publish and manage events, post and manage comments, participate to events, manage all of your groups, including group events, resources, posts and discussions. It will also be allowed to manage your account and profile settings.": "This application will be allowed to publish and manage events, post and manage comments, participate to events, manage all of your groups, including group events, resources, posts and discussions. It will also be allowed to manage your account and profile settings.",
"No apps authorized yet": "No apps authorized yet",
"You have been logged-out": "You have been logged-out",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely."
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.",
"Announcements": "Announcements"
}

View file

@ -1560,5 +1560,6 @@
"This application will be allowed to publish and manage events, post and manage comments, participate to events, manage all of your groups, including group events, resources, posts and discussions. It will also be allowed to manage your account and profile settings.": "Cette application sera autorisée à publier et à gérer des événements, à publier et à gérer des commentaires, à participer à des événements, à gérer tous vos groupes, y compris les événements de groupe, les ressources, les messages et les discussions. Elle pourra également gérer les paramètres de votre compte et de votre profil.",
"No apps authorized yet": "Aucune application autorisée pour le moment",
"You have been logged-out": "Vous avez été déconnecté·e",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "Une « interface de programmation dapplication » ou « API » est un protocole de communication qui permet aux composants logiciels de communiquer entre eux. L'API Mobilizon, par exemple, peut permettre à des outils logiciels tiers de communiquer avec les instances Mobilizon pour effectuer certaines actions, telles que la publication d'événements en votre nom, automatiquement et à distance."
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events on your behalf, automatically and remotely.": "Une « interface de programmation dapplication » ou « API » est un protocole de communication qui permet aux composants logiciels de communiquer entre eux. L'API Mobilizon, par exemple, peut permettre à des outils logiciels tiers de communiquer avec les instances Mobilizon pour effectuer certaines actions, telles que la publication d'événements en votre nom, automatiquement et à distance.",
"Announcements": "Annonces"
}

View file

@ -215,7 +215,7 @@ import { IPerson, displayName } from "@/types/actor";
import PictureUpload from "@/components/PictureUpload.vue";
import { MOBILIZON_INSTANCE_HOST } from "@/api/_entrypoint";
import RouteName from "@/router/name";
import { buildFileVariable } from "@/utils/image";
import { buildFileFromIMedia, buildFileVariable } from "@/utils/image";
import { changeIdentity } from "@/utils/identity";
import {
CREATE_FEED_TOKEN_ACTOR,
@ -237,6 +237,7 @@ import { Dialog } from "@/plugins/dialog";
import { Notifier } from "@/plugins/notifier";
import { AbsintheGraphQLErrors } from "@/types/errors.model";
import { ICurrentUser } from "@/types/current-user.model";
import { useHead } from "@vueuse/head";
const { t } = useI18n({ useScope: "global" });
const router = useRouter();
@ -245,7 +246,11 @@ const props = defineProps<{ isUpdate: boolean; identityName?: string }>();
const { currentActor } = useCurrentActorClient();
const { result: personResult, onError: onPersonError } = useQuery<{
const {
result: personResult,
onError: onPersonError,
onResult: onPersonResult,
} = useQuery<{
fetchPerson: IPerson;
}>(
FETCH_PERSON,
@ -257,6 +262,10 @@ const { result: personResult, onError: onPersonError } = useQuery<{
})
);
onPersonResult(async ({ data }) => {
avatarFile.value = await buildFileFromIMedia(data?.fetchPerson?.avatar);
});
onPersonError((err) => handleErrors(err as unknown as AbsintheGraphQLErrors));
const person = computed(() => personResult.value?.fetchPerson);
@ -285,24 +294,6 @@ watch(person, () => {
const avatarMaxSize = useAvatarMaxSize();
// error({ graphQLErrors }) {
// this.handleErrors(graphQLErrors);
// },
// metaInfo() {
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// // @ts-ignore
// const { isUpdate, identityName } = this;
// let title = t("Create a new profile") as string;
// if (isUpdate) {
// title = t("Edit profile {profile}", {
// profile: identityName,
// }) as string;
// }
// return {
// title,
// };
// },
const errors = ref<string[]>([]);
const avatarFile = ref<File | null>(null);
const showCopiedTooltip = reactive({ ics: false, atom: false });
@ -764,4 +755,16 @@ const breadcrumbsLinks = computed(
const updateUsername = (value: string) => {
identity.value.preferredUsername = convertToUsername(value);
};
useHead({
title: computed(() => {
let title = t("Create a new profile") as string;
if (isUpdate.value) {
title = t("Edit profile {profile}", {
profile: identityName.value,
}) as string;
}
return title;
}),
});
</script>

View file

@ -102,7 +102,7 @@ import RouteName from "@/router/name";
import EmptyContent from "@/components/Utils/EmptyContent.vue";
import { useQuery } from "@vue/apollo-composable";
import { useI18n } from "vue-i18n";
import { computed, ref } from "vue";
import { computed } from "vue";
import { useHead } from "@vueuse/head";
import {
useRouteQuery,

View file

@ -483,6 +483,7 @@
<span class="" v-if="event.draft === true">
<o-button
variant="primary"
class="!text-black hover:!text-white"
outlined
@click="createOrUpdateDraft"
:disabled="saving"

View file

@ -196,7 +196,7 @@ import RouteName from "@/router/name";
import { buildFileFromIMedia } from "@/utils/image";
import { useAvatarMaxSize, useBannerMaxSize } from "@/composition/config";
import { useI18n } from "vue-i18n";
import { computed, ref, watch, defineAsyncComponent, inject } from "vue";
import { computed, ref, defineAsyncComponent, inject } from "vue";
import { useGroup, useUpdateGroup } from "@/composition/apollo/group";
import {
useCurrentActorClient,
@ -262,38 +262,17 @@ const copyURL = async (): Promise<void> => {
}, 2000);
};
onGroupResult(({ data }) => {
onGroupResult(async ({ data }) => {
if (!data) return;
editableGroup.value = data.group;
});
watch(
group,
async (newGroup: IGroup | undefined, oldGroup: IGroup | undefined) => {
console.debug("watching group");
if (!newGroup) return;
try {
if (
oldGroup?.avatar !== undefined &&
oldGroup?.avatar !== newGroup?.avatar
) {
avatarFile.value = await buildFileFromIMedia(newGroup?.avatar);
}
if (
oldGroup?.banner !== undefined &&
oldGroup?.banner !== newGroup?.banner
) {
bannerFile.value = await buildFileFromIMedia(newGroup?.banner);
}
} catch (e) {
// Catch errors while building media
console.error(e);
}
editableGroup.value = { ...newGroup };
},
{
immediate: true,
try {
avatarFile.value = await buildFileFromIMedia(editableGroup.value?.avatar);
bannerFile.value = await buildFileFromIMedia(editableGroup.value?.banner);
} catch (e) {
// Catch errors while building media
console.error(e);
}
);
});
const buildVariables = computed(() => {
let avatarObj = {};

View file

@ -78,13 +78,16 @@
})
}}
</span>
<span v-if="post.visibility === PostVisibility.UNLISTED" class="">
<o-icon icon="link" size="small" />
<span
v-if="post.visibility === PostVisibility.UNLISTED"
class="flex gap-2 items-center"
>
<Link :size="16" />
{{ t("Accessible only by link") }}
</span>
<span
v-else-if="post.visibility === PostVisibility.PRIVATE"
class=""
class="flex gap-2 items-center"
>
<Lock :size="16" />
{{
@ -275,6 +278,7 @@ import Pencil from "vue-material-design-icons/Pencil.vue";
import Delete from "vue-material-design-icons/Delete.vue";
import Share from "vue-material-design-icons/Share.vue";
import Flag from "vue-material-design-icons/Flag.vue";
import Link from "vue-material-design-icons/Link.vue";
import { Dialog } from "@/plugins/dialog";
import { useI18n } from "vue-i18n";
import { Notifier } from "@/plugins/notifier";

View file

@ -4,7 +4,7 @@ import { createRouter, createWebHistory, Router } from "vue-router";
import { routes } from "@/router";
import { CommentModeration, EventJoinOptions } from "@/types/enums";
import { InMemoryCache } from "@apollo/client/cache";
import { beforeEach, describe, expect, vi, it } from "vitest";
import { beforeEach, describe, expect, it } from "vitest";
import Oruga from "@oruga-ui/oruga-next";
import FloatingVue from "floating-vue";
@ -34,12 +34,7 @@ const eventData = {
describe("ParticipationSection", () => {
let wrapper: VueWrapper;
const generateWrapper = (
customProps: Record<string, unknown> = {},
baseData: Record<string, unknown> = {}
) => {
const cache = new InMemoryCache({ addTypename: false });
const generateWrapper = (customProps: Record<string, unknown> = {}) => {
wrapper = mount(ParticipationSection, {
stubs: {
ParticipationButton: true,

View file

@ -1,6 +1,6 @@
import { config, mount } from "@vue/test-utils";
import { mount } from "@vue/test-utils";
import PostListItem from "@/components/Post/PostListItem.vue";
import { vi, beforeEach, describe, it, expect } from "vitest";
import { beforeEach, describe, it, expect } from "vitest";
import { enUS } from "date-fns/locale";
import { routes } from "@/router";
import { createRouter, createWebHistory, Router } from "vue-router";

View file

@ -1,9 +1,7 @@
import { config, mount } from "@vue/test-utils";
import { mount } from "@vue/test-utils";
import ReportCard from "@/components/Report/ReportCard.vue";
import { ActorType } from "@/types/enums";
import { describe, expect, it } from "vitest";
import { createI18n } from "vue-i18n";
import en from "@/i18n/en_US.json";
const reportData = {
id: "1",

View file

@ -1,5 +1,7 @@
const useRouterMock = vi.fn(() => ({
push: () => {},
push: function () {
// do nothing
},
}));
import { config, mount } from "@vue/test-utils";

View file

@ -1,8 +1,14 @@
const useRouterMock = vi.fn(() => ({
push: () => {},
replace: () => {},
push: function () {
// do nothing
},
replace: function () {
// do nothing
},
}));
const useRouteMock = vi.fn(() => {});
const useRouteMock = vi.fn(function () {
// do nothing
});
import { config, mount, VueWrapper } from "@vue/test-utils";
import Login from "@/views/User/LoginView.vue";

View file

@ -1,5 +1,7 @@
const useRouterMock = vi.fn(() => ({
push: () => {},
push: function () {
// do nothing
},
}));
import { shallowMount, VueWrapper } from "@vue/test-utils";
@ -21,7 +23,6 @@ vi.mock("vue-router/dist/vue-router.mjs", () => ({
describe("App component", () => {
let wrapper: VueWrapper;
let mockClient: MockApolloClient | null;
let requestHandlers: Record<string, RequestHandler>;
const createComponent = (handlers = {}) => {
const cache = new InMemoryCache({ addTypename: false });
@ -31,8 +32,6 @@ describe("App component", () => {
resolvers: buildCurrentUserResolver(cache),
});
requestHandlers = { ...handlers };
wrapper = shallowMount(NavBar, {
// stubs: ["router-link", "router-view", "o-dropdown", "o-dropdown-item"],
global: {

File diff suppressed because it is too large Load diff

View file

@ -80,6 +80,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
}
} = _resolution
) do
Logger.debug("Getting resource for group with username #{username}")
with {:group, %Actor{id: group_id}} <- {:group, Actors.get_actor_by_name(username, :Group)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:resource, %Resource{} = resource} <-
@ -222,13 +224,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
{:ok, data} when is_map(data) ->
{:ok, struct(Metadata, data)}
{:error, :invalid_parsed_data} ->
{:error, _error_type, _} ->
{: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

@ -29,19 +29,19 @@ defmodule Mobilizon.Service.RichMedia.Parser do
def parse(nil), do: {:error, "No URL provided"}
@spec parse(String.t()) :: {:ok, map()} | {:error, any()}
@spec parse(String.t()) :: {:ok, map()} | {:error, :http | :parsing | :unknown, any()}
def parse(url) do
case Cachex.fetch(:rich_media_cache, url, fn _ ->
case parse_url(url) do
{:ok, data} -> {:commit, data}
{:error, err} -> {:ignore, err}
{:error, error_type, error} -> {:ignore, {error_type, error}}
end
end) do
{status, value} when status in [:ok, :commit] ->
{:ok, value}
{_, err} ->
{:error, err}
{_, {error_type, err}} ->
{:error, error_type, err}
end
rescue
e ->
@ -56,7 +56,8 @@ defmodule Mobilizon.Service.RichMedia.Parser do
get_filename_from_headers(response_headers) || get_filename_from_url(url)
end
@spec parse_url(String.t(), Enum.t()) :: {:ok, map()} | {:error, any()}
@spec parse_url(String.t(), Enum.t()) ::
{:ok, map()} | {:error, :http | :parsing | :unknown, any()}
defp parse_url(url, options \\ []) do
user_agent = Keyword.get(options, :user_agent, default_user_agent(url))
headers = [{"User-Agent", user_agent}]
@ -64,13 +65,14 @@ defmodule Mobilizon.Service.RichMedia.Parser do
try do
with {:ok, _} <- prevent_local_address(url),
{:ok, %{body: body, status: code, headers: response_headers}}
{:fetch, {:ok, %{body: body, status: code, headers: response_headers}}}
when code in 200..299 <-
RichMediaPreviewClient.get(
url,
headers: headers,
opts: @options
),
{:fetch,
RichMediaPreviewClient.get(
url,
headers: headers,
opts: @options
)},
{:is_html, _response_headers, true} <-
{:is_html, response_headers, is_html(response_headers)} do
body
@ -87,17 +89,17 @@ defmodule Mobilizon.Service.RichMedia.Parser do
{:ok, data}
{:ok, err} ->
{:fetch, {_, err}} ->
Logger.debug("HTTP error: #{inspect(err)}")
{:error, "HTTP error: #{inspect(err)}"}
{:error, :http, err}
{:error, err} ->
Logger.debug("HTTP error: #{inspect(err)}")
{:error, "HTTP error: #{inspect(err)}"}
Logger.debug("Parsing error: #{inspect(err)}")
{:error, :parsing, err}
end
rescue
e ->
{:error, "Parsing error: #{inspect(e)} #{inspect(__STACKTRACE__)}"}
{:error, :unknown, "Parsing error: #{inspect(e)} #{inspect(__STACKTRACE__)}"}
end
end
@ -228,7 +230,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
check_parsed_data(data, html, false)
else
Logger.debug("Found metadata was invalid or incomplete: #{inspect(data)}")
{:error, :invalid_parsed_data}
{:error, :parsing, :invalid_parsed_data}
end
end
@ -252,11 +254,11 @@ defmodule Mobilizon.Service.RichMedia.Parser do
validate_ip(host) do
{:ok, url}
else
{:error, "Host violates local access rules"}
{:error, :local_address, "Host violates local access rules"}
end
_ ->
{:error, "Could not detect any host"}
{:error, :no_host, "Could not detect any host"}
end
end
@ -302,6 +304,8 @@ defmodule Mobilizon.Service.RichMedia.Parser do
{:ok, data}
end
defp check_remote_picture_path({:error, _, _} = err), do: err
defp check_remote_picture_path(data), do: {:ok, data}
@spec format_url(String.t(), String.t()) :: String.t()

View file

@ -67,8 +67,8 @@ defmodule Mobilizon.Service.RichMedia.Parsers.OEmbed do
{:ok, data} <- Jason.decode(json),
data <-
data
|> Map.new(fn {k, v} -> {String.to_existing_atom(k), String.trim(v)} end)
|> Map.take(@oembed_allowed_attributes) do
|> Map.new(fn {k, v} -> {k, if(is_binary(v), do: String.trim(v), else: v)} end)
|> Map.take(Enum.map(@oembed_allowed_attributes, &to_string/1)) do
{:ok, data}
end
end

View file

@ -133,7 +133,7 @@ defmodule Mobilizon.Mixfile do
{:phoenix_ecto, "~> 4.0"},
{:postgrex, ">= 0.17.1"},
{:phoenix_html, "~> 3.0"},
{:phoenix_live_view, "~> 0.18.3"},
{:phoenix_live_view, "~> 0.19.0"},
{:phoenix_view, "~> 2.0"},
{:gettext, "~> 0.20.0"},
{:cowboy, "~> 2.6"},

View file

@ -8,7 +8,7 @@
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [: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", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"castore": {:hex, :castore, "1.0.2", "0c6292ecf3e3f20b7c88408f00096337c4bfd99bd46cc2fe63413ddbe45b3573", [:mix], [], "hexpm", "40b2dd2836199203df8500e4a270f10fc006cc95adc8a319e148dc3077391d96"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"cldr_utils": {:hex, :cldr_utils, "2.23.1", "5c7df90f10b2ffe2519124f3c0cba1bfc0a303d91c34c2e130e86c4f7bf1840c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [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", "42242882f76499fc28e59c80da8b832904cbe68ea50eeb42e65350b0dae7640c"},
"cldr_utils": {:hex, :cldr_utils, "2.24.0", "5b356769f2baf9dd25b166e9f1b2a5ce955d5b556bc07f56c959cfff1ec9543e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [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", "3fde251e0bbbe473230561019dd6c2958d82a10c134bb5b8b4e21a694196cf85"},
"codepagex": {:hex, :codepagex, "0.1.6", "49110d09a25ee336a983281a48ef883da4c6190481e0b063afe2db481af6117e", [:mix], [], "hexpm", "1521461097dde281edf084062f525a4edc6a5e49f4fd1f5ec41c9c4955d5bd59"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
@ -36,7 +36,7 @@
"elixir_feed_parser": {:hex, :elixir_feed_parser, "2.1.0", "bb96fb6422158dc7ad59de62ef211cc69d264acbbe63941a64a5dce97bbbc2e6", [:mix], [{:timex, "~> 3.4", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "2d3c62fe7b396ee3b73d7160bc8fadbd78bfe9597c98c7d79b3f1038d9cba28f"},
"elixir_make": {:hex, :elixir_make, "0.7.6", "67716309dc5d43e16b5abbd00c01b8df6a0c2ab54a8f595468035a50189f9169", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5a0569756b0f7873a77687800c164cca6dfc03a09418e6fcf853d78991f49940"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"erlport": {:git, "https://github.com/tcitworld/erlport.git", "04bcfd732fa458735001328b44e8b3a1764316a5", [branch: "0.10.1-compat"]},
"erlport": {:git, "https://github.com/tcitworld/erlport.git", "1f8f4b1a50ecdf7e959090fb566ac45c63c39b0b", [branch: "0.10.1-compat"]},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
"ex_cldr": {:hex, :ex_cldr, "2.37.1", "6091fa719a7a96f9abee7aba186e63a906d504d08039cc8f0c683a0e71ee1bd7", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "5d60c3288454bc966e404ea4f59531f7dbb570d7e927dce62f0ab8466713bf78"},
"ex_cldr_calendars": {:hex, :ex_cldr_calendars, "1.22.0", "016373c6725682851d752b72585581154926b2c8a20ee43e418479911dae0ff9", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: true]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_cldr_units, "~> 3.16", [hex: :ex_cldr_units, repo: "hexpm", optional: true]}, {:ex_doc, "~> 0.21", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c1c86de086009dce5786eb10fed0d351e52a844307c2ff0e83531f91d3268e8b"},
@ -110,8 +110,8 @@
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.1", "fe7a02387a7d26002a46b97e9879591efee7ebffe5f5e114fd196632e6e4a08d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ddccf8b4966180afe7630b105edb3402b1ca485e7468109540d262e842048ba4"},
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.19.0", "de5643d03e3cdf5ff19cd45b5d14543a3b1ad8551d529f6b24246e88a6c6f1b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a650b6f814c4f386314b98f1aebf92f8652649166612f84ef2e60a20894addfa"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.2", "a4950b63ace57720b0fc1c6da083c53346b36f99021de89595cc4f026288ff51", [:mix], [], "hexpm", "45741676a94c71f9afdfed9d22d49b6856c026ff504db04e3dc03a1d86f8201c"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"},
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
@ -134,7 +134,7 @@
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"struct_access": {:hex, :struct_access, "1.1.2", "a42e6ceedd9b9ea090ee94a6da089d56e16f374dbbc010c3eebdf8be17df286f", [:mix], [], "hexpm", "e4c411dcc0226081b95709909551fc92b8feb1a3476108348ea7e3f6c12e586a"},
"sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"},
"swoosh": {:hex, :swoosh, "1.10.2", "77acdc1261de404b893e24224d47459d1b42deb02577c7b31514e0a720f949d6", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1736faf374ed49c6091845cdfd5b3a68c88c5f2bfd989447d12bffafc0dda03a"},
"swoosh": {:hex, :swoosh, "1.11.0", "00b4fff8c08347a47cc5cbe67d64df5aae0607a7a51171944f5b89216e2d62f5", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5e7c49b6259e50a5ed756517e23a7f916c0b73eb0752e864b1d83b28e2c204d9"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [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 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"},
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
@ -144,7 +144,7 @@
"ueberauth_cas": {:hex, :ueberauth_cas, "2.3.1", "df45a1f2c5df8bc80191cbca4baeeed808d697702ec5ebe5bd5d5a264481752f", [:mix], [{:httpoison, "~> 1.8", [hex: :httpoison, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "5068ae2b9e217c2f05aa9a67483a6531e21ba0be9a6f6c8749bb7fd1599be321"},
"ueberauth_discord": {:hex, :ueberauth_discord, "0.7.0", "463f6dfe1ed10a76739331ce8e1dd3600ab611f10524dd828eb3aa50e76e9d43", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "d6f98ef91abb4ddceada4b7acba470e0e68c4d2de9735ff2f24172a8e19896b4"},
"ueberauth_facebook": {:hex, :ueberauth_facebook, "0.10.0", "0d607fbd1b7c6e0449981571027d869c2d156b8ad20c42e3672346678c05ccf1", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "bf8ce5d66b1c50da8abff77e8086c1b710bdde63f4acaef19a651ba43a9537a8"},
"ueberauth_github": {:hex, :ueberauth_github, "0.8.2", "6e12332172ebfadb6fbe2feaeddbd8c421f8ebf9d425d1367c2dce2a35f6bf21", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "98ed414ee313bd754135ab9689e8f16863c5691d11c8526800457cce6153d4e2"},
"ueberauth_github": {:hex, :ueberauth_github, "0.8.3", "1c478629b4c1dae446c68834b69194ad5cead3b6c67c913db6fdf64f37f0328f", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "ae0ab2879c32cfa51d7287a48219b262bfdab0b7ec6629f24160564247493cc6"},
"ueberauth_gitlab_strategy": {:hex, :ueberauth_gitlab_strategy, "0.4.0", "96605d304ebb87ce508eccbeb1f94da9ea1c9da20d8913771b6cf24a6cc6c633", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "e86e2e794bb063c07c05a6b1301b73f2be3ba9308d8f47ecc4d510ef9226091e"},
"ueberauth_google": {:hex, :ueberauth_google, "0.10.2", "b85b3de6070e7bc71bbec3d4dbe2de805b004ae9c19efeb31531f9134ede4033", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.10.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "fcf987749db5e2d890240ce61223c61ee6ac1d638c3378bf1eeeb0e6332e5a12"},
"ueberauth_keycloak_strategy": {:hex, :ueberauth_keycloak_strategy, "0.4.0", "51e975874564ef4a6eb0044b9f0c6a08be4ba6086e62e41d385e7dd52fe9568b", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "c03027937bddcbd9ff499e457f9bb05f79018fa321abf79ebcfed2af0007211b"},