Merge branch 'fixes' into 'main'

Various apps fixes

See merge request framasoft/mobilizon!1401
This commit is contained in:
Thomas Citharel 2023-05-30 12:53:23 +00:00
commit 0812890d2f
8 changed files with 94 additions and 17 deletions

View file

@ -40,4 +40,5 @@ FE1EEB91EA633570F703B251AE2D4D4E
7EEC79571F3F7CEEB04A8B86D908382A 7EEC79571F3F7CEEB04A8B86D908382A
E7967805C1EA5301F2722C7BDB2F25F3 E7967805C1EA5301F2722C7BDB2F25F3
BDFB0FB1AAF69C18212CBCFD42F8B717 BDFB0FB1AAF69C18212CBCFD42F8B717
40220A533CCACB3A1CE9DBF1A8A430A1 40220A533CCACB3A1CE9DBF1A8A430A1
EEB29D1DDA3A3015BC645A989B5BD38E

View file

@ -1,5 +1,15 @@
<template> <template>
<div> <div v-if="checkDevice">
<div class="rounded-lg bg-white dark:bg-zinc-900 shadow-xl my-6 p-4 pt-1">
<h1 class="text-3xl">
{{ t("Application authorized") }}
</h1>
<p>
{{ t("Check your device to continue. You may now close this window.") }}
</p>
</div>
</div>
<div v-else>
<h1 class="text-3xl"> <h1 class="text-3xl">
{{ t("Autorize this application to access your account?") }} {{ t("Autorize this application to access your account?") }}
</h1> </h1>
@ -68,7 +78,7 @@
<div class="flex gap-3 p-4"> <div class="flex gap-3 p-4">
<o-button <o-button
:disabled="collapses.length === 0" :disabled="collapses.length === 0"
@click="() => authorize()" @click="() => (userCode ? authorizeDevice() : authorize())"
>{{ t("Authorize") }}</o-button >{{ t("Authorize") }}</o-button
> >
<o-button outlined tag="router-link" :to="{ name: RouteName.HOME }">{{ <o-button outlined tag="router-link" :to="{ name: RouteName.HOME }">{{
@ -84,7 +94,10 @@ import { useHead } from "@vueuse/head";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useMutation } from "@vue/apollo-composable"; import { useMutation } from "@vue/apollo-composable";
import { AUTORIZE_APPLICATION } from "@/graphql/application"; import {
AUTORIZE_APPLICATION,
AUTORIZE_DEVICE_APPLICATION,
} from "@/graphql/application";
import RouteName from "@/router/name"; import RouteName from "@/router/name";
import { IApplication } from "@/types/application.model"; import { IApplication } from "@/types/application.model";
import { scope as oAuthScopes } from "./scopes"; import { scope as oAuthScopes } from "./scopes";
@ -97,9 +110,11 @@ const props = defineProps<{
redirectURI?: string | null; redirectURI?: string | null;
state?: string | null; state?: string | null;
scope?: string | null; scope?: string | null;
userCode?: string;
}>(); }>();
const isOpen = ref<number>(-1); const isOpen = ref<number>(-1);
const checkDevice = ref(false);
const collapses = computed(() => const collapses = computed(() =>
(props.scope ?? "") (props.scope ?? "")
@ -135,6 +150,37 @@ const authorize = () => {
}); });
}; };
const {
mutate: authorizeDeviceMutation,
onDone: onAuthorizeDeviceMutationDone,
} = useMutation<
{
authorizeDeviceApplication: {
clientId: string;
scope: string;
};
},
{
applicationClientId: string;
userCode: string;
}
>(AUTORIZE_DEVICE_APPLICATION);
const authorizeDevice = () => {
authorizeDeviceMutation({
applicationClientId: props.authApplication.clientId,
userCode: props.userCode ?? "",
});
};
onAuthorizeDeviceMutationDone(({ data }) => {
const localClientId = data?.authorizeDeviceApplication?.clientId;
const localScope = data?.authorizeDeviceApplication?.scope;
if (!localClientId || !localScope) return;
checkDevice.value = true;
});
onAuthorizeMutationDone(({ data }) => { onAuthorizeMutationDone(({ data }) => {
const code = data?.authorizeApplication?.code; const code = data?.authorizeApplication?.code;
const localClientId = data?.authorizeApplication?.clientId; const localClientId = data?.authorizeApplication?.clientId;
@ -143,6 +189,11 @@ onAuthorizeMutationDone(({ data }) => {
if (!code || !localClientId || !localScope) return; if (!code || !localClientId || !localScope) return;
if (props.redirectURI === "urn:ietf:wg:oauth:2.0:oob") {
checkDevice.value = true;
return;
}
if (props.redirectURI) { if (props.redirectURI) {
const params = new URLSearchParams( const params = new URLSearchParams(
Object.entries({ Object.entries({

View file

@ -35,7 +35,7 @@ export const AUTORIZE_APPLICATION = gql`
export const AUTORIZE_DEVICE_APPLICATION = gql` export const AUTORIZE_DEVICE_APPLICATION = gql`
mutation AuthorizeDeviceApplication( mutation AuthorizeDeviceApplication(
$applicationClientId: String! $applicationClientId: String!
$userCode: String $userCode: String!
) { ) {
authorizeDeviceApplication( authorizeDeviceApplication(
clientId: $applicationClientId clientId: $applicationClientId

View file

@ -57,7 +57,12 @@
<o-button native-type="submit">{{ t("Continue") }}</o-button> <o-button native-type="submit">{{ t("Continue") }}</o-button>
</div> </div>
</form> </form>
<AuthorizeApplication v-if="application" :auth-application="application" /> <AuthorizeApplication
v-if="application"
:auth-application="application"
:user-code="code"
:scope="scope"
/>
</div> </div>
</template> </template>
@ -85,11 +90,14 @@ const {
const inputs = reactive<string[]>([]); const inputs = reactive<string[]>([]);
const application = ref<IApplication | null>(null); const application = ref<IApplication | null>(null);
const scope = ref<string | null>(null);
onDeviceActivationDone(({ data }) => { onDeviceActivationDone(({ data }) => {
console.debug("onDeviceActivationDone", data);
const foundApplication = data?.deviceActivation?.application; const foundApplication = data?.deviceActivation?.application;
if (foundApplication) { if (foundApplication) {
application.value = foundApplication; application.value = foundApplication;
scope.value = data?.deviceActivation?.scope;
} }
}); });
@ -114,7 +122,11 @@ const error = ref<string | null>(null);
onDeviceActivationError( onDeviceActivationError(
({ graphQLErrors }: { graphQLErrors: AbsintheGraphQLErrors }) => { ({ graphQLErrors }: { graphQLErrors: AbsintheGraphQLErrors }) => {
if (graphQLErrors[0].status_code === 404) { const err = graphQLErrors[0];
if (
err.status_code === 400 &&
err.code === "device_application_code_expired"
) {
error.value = t("The device code is incorrect or no longer valid."); error.value = t("The device code is incorrect or no longer valid.");
} }
resetInputs(); resetInputs();

View file

@ -5,6 +5,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Application do
alias Mobilizon.Applications, as: ApplicationManager alias Mobilizon.Applications, as: ApplicationManager
alias Mobilizon.Applications.{Application, ApplicationDeviceActivation, ApplicationToken} alias Mobilizon.Applications.{Application, ApplicationDeviceActivation, ApplicationToken}
alias Mobilizon.GraphQL.Error
alias Mobilizon.Service.Auth.Applications alias Mobilizon.Service.Auth.Applications
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext, only: [dgettext: 2] import Mobilizon.Web.Gettext, only: [dgettext: 2]
@ -17,7 +18,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Application do
@spec authorize(any(), map(), Absinthe.Resolution.t()) :: {:ok, map()} | {:error, String.t()} @spec authorize(any(), map(), Absinthe.Resolution.t()) :: {:ok, map()} | {:error, String.t()}
def authorize( def authorize(
_parent, _parent,
%{client_id: client_id, redirect_uri: redirect_uri, scope: scope, state: state}, %{client_id: client_id, redirect_uri: redirect_uri, scope: scope} = args,
%{context: %{current_user: %User{id: user_id}}} %{context: %{current_user: %User{id: user_id}}}
) do ) do
case Applications.autorize(client_id, redirect_uri, scope, user_id) do case Applications.autorize(client_id, redirect_uri, scope, user_id) do
@ -27,7 +28,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Application do
scope: scope, scope: scope,
authorization_code: code authorization_code: code
}} -> }} ->
{:ok, %{code: code, state: state, client_id: client_id, scope: scope}} {:ok, %{code: code, state: Map.get(args, :state), client_id: client_id, scope: scope}}
{:error, %Ecto.Changeset{} = err} -> {:error, %Ecto.Changeset{} = err} ->
{:error, err} {:error, err}
@ -106,7 +107,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Application do
{:ok, app_device_activation |> Map.from_struct() |> Map.take([:application, :id, :scope])} {:ok, app_device_activation |> Map.from_struct() |> Map.take([:application, :id, :scope])}
{:error, :expired} -> {:error, :expired} ->
{:error, dgettext("errors", "The given user code has expired")} {:error,
%Error{
message: dgettext("errors", "The given user code has expired"),
status_code: 400,
code: :device_application_code_expired
}}
{:error, :not_found} -> {:error, :not_found} ->
{:error, dgettext("errors", "The given user code is invalid")} {:error, dgettext("errors", "The given user code is invalid")}
@ -143,7 +149,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Application do
)} )}
{:error, :expired} -> {:error, :expired} ->
{:error, dgettext("errors", "The given user code has expired")} {:error,
%Error{
message: dgettext("errors", "The given user code has expired"),
status_code: 400,
code: :device_application_code_expired
}}
end end
end end

View file

@ -111,7 +111,7 @@ defmodule Mobilizon.GraphQL.Schema.AuthApplicationType do
resolve(&Application.activate_device/3) resolve(&Application.activate_device/3)
end end
@desc "Activate an user device" @desc "Authorize an user device"
field :authorize_device_application, :auth_application do field :authorize_device_application, :auth_application do
arg(:client_id, non_null(:string), description: "The application's client_id") arg(:client_id, non_null(:string), description: "The application's client_id")

View file

@ -13,7 +13,7 @@ defmodule Mobilizon.Web.ApplicationController do
@spec create_application(Plug.Conn.t(), map()) :: Plug.Conn.t() @spec create_application(Plug.Conn.t(), map()) :: Plug.Conn.t()
def create_application( def create_application(
conn, conn,
%{"name" => name, "redirect_uris" => redirect_uris, "scope" => scope} = args %{"name" => name, "redirect_uri" => redirect_uris, "scope" => scope} = args
) do ) do
ip = conn.remote_ip |> :inet.ntoa() |> to_string() ip = conn.remote_ip |> :inet.ntoa() |> to_string()
@ -33,7 +33,9 @@ defmodule Mobilizon.Web.ApplicationController do
conn conn
|> Plug.Conn.put_resp_header("cache-control", "no-store") |> Plug.Conn.put_resp_header("cache-control", "no-store")
|> json( |> json(
Map.take(app, [:name, :website, :redirect_uris, :client_id, :client_secret, :scope]) app
|> Map.take([:name, :website, :client_id, :client_secret, :scope])
|> Map.put(:redirect_uri, app.redirect_uris)
) )
{:error, :invalid_scope} -> {:error, :invalid_scope} ->

View file

@ -24,7 +24,7 @@ defmodule Mobilizon.Web.ApplicationControllerTest do
conn conn
|> post("/apps", %{ |> post("/apps", %{
"name" => "hello", "name" => "hello",
"redirect_uris" => "hello", "redirect_uri" => "hello",
"scope" => "write nothing" "scope" => "write nothing"
}) })
@ -46,14 +46,14 @@ defmodule Mobilizon.Web.ApplicationControllerTest do
conn conn
|> post("/apps", %{ |> post("/apps", %{
"name" => name, "name" => name,
"redirect_uris" => Enum.join(redirect_uris, "\n"), "redirect_uri" => Enum.join(redirect_uris, "\n"),
"scope" => scope, "scope" => scope,
"website" => website "website" => website
}) })
assert %{ assert %{
"name" => ^name, "name" => ^name,
"redirect_uris" => ^redirect_uris, "redirect_uri" => ^redirect_uris,
"scope" => ^scope, "scope" => ^scope,
"website" => ^website, "website" => ^website,
"client_id" => _client_id, "client_id" => _client_id,