diff --git a/config/config.exs b/config/config.exs index ec681430a..8808913fe 100644 --- a/config/config.exs +++ b/config/config.exs @@ -120,14 +120,19 @@ config :logger, Sentry.LoggerBackend, level: :warn, capture_log_messages: true -config :mobilizon, Mobilizon.Web.Auth.Guardian, issuer: "mobilizon" +config :mobilizon, Mobilizon.Web.Auth.Guardian, + issuer: "mobilizon", + token_ttl: %{ + "access" => {15, :minutes}, + "refresh" => {60, :days} + } config :guardian, Guardian.DB, repo: Mobilizon.Storage.Repo, # default schema_name: "guardian_tokens", # store all token types if not set - # token_types: ["refresh_token"], + token_types: ["refresh"], # default: 60 minutes sweep_interval: 60 diff --git a/js/src/graphql/auth.ts b/js/src/graphql/auth.ts index ccbd73207..e2b0d38c0 100644 --- a/js/src/graphql/auth.ts +++ b/js/src/graphql/auth.ts @@ -46,3 +46,9 @@ export const REFRESH_TOKEN = gql` } } `; + +export const LOGOUT = gql` + mutation Logout($refreshToken: String!) { + logout(refreshToken: $refreshToken) + } +`; diff --git a/js/src/utils/auth.ts b/js/src/utils/auth.ts index b59efefcd..2feb49244 100644 --- a/js/src/utils/auth.ts +++ b/js/src/utils/auth.ts @@ -14,6 +14,7 @@ import { IPerson } from "@/types/actor"; import { IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; import { ICurrentUserRole } from "@/types/enums"; import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types"; +import { LOGOUT } from "@/graphql/auth"; export function saveTokenData(obj: IToken): void { localStorage.setItem(AUTH_ACCESS_TOKEN, obj.accessToken); @@ -96,6 +97,13 @@ export async function initializeCurrentActor( export async function logout( apollo: ApolloClient ): Promise { + await apollo.mutate({ + mutation: LOGOUT, + variables: { + refreshToken: localStorage.getItem(AUTH_REFRESH_TOKEN), + }, + }); + await apollo.mutate({ mutation: UPDATE_CURRENT_USER_CLIENT, variables: { diff --git a/js/src/views/Settings/Notifications.vue b/js/src/views/Settings/Notifications.vue index e4e3a998c..c27117e47 100644 --- a/js/src/views/Settings/Notifications.vue +++ b/js/src/views/Settings/Notifications.vue @@ -385,7 +385,7 @@ export default class Notifications extends Vue { private async isSubscribed(): Promise { if (!("serviceWorker" in navigator)) return Promise.resolve(false); const registration = await navigator.serviceWorker.getRegistration(); - return (await registration?.pushManager.getSubscription()) !== null; + return (await registration?.pushManager.getSubscription()) != null; } private async deleteFeedToken(token: string): Promise { diff --git a/lib/graphql/resolvers/user.ex b/lib/graphql/resolvers/user.ex index 82a4dde34..ddcada5f6 100644 --- a/lib/graphql/resolvers/user.ex +++ b/lib/graphql/resolvers/user.ex @@ -105,6 +105,28 @@ defmodule Mobilizon.GraphQL.Resolvers.User do {:error, dgettext("errors", "You need to have an existing token to get a refresh token")} end + def logout(_parent, %{refresh_token: refresh_token}, %{context: %{current_user: %User{}}}) do + with {:ok, _claims} <- Auth.Guardian.decode_and_verify(refresh_token, %{"typ" => "refresh"}), + {:ok, _claims} <- Auth.Guardian.revoke(refresh_token) do + {:ok, refresh_token} + else + {:error, :token_not_found} -> + {:error, :token_not_found} + + {:error, error} -> + Logger.debug("Cannot remove user refresh token: #{inspect(error)}") + {:error, :unable_to_logout} + end + end + + def logout(_parent, %{refresh_token: _refresh_token}, _context) do + {:error, :unauthenticated} + end + + def logout(_parent, _params, _context) do + {:error, :invalid_argument} + end + @doc """ Register an user: - check registrations are enabled diff --git a/lib/graphql/schema/user.ex b/lib/graphql/schema/user.ex index e8236c897..8d713464d 100644 --- a/lib/graphql/schema/user.ex +++ b/lib/graphql/schema/user.ex @@ -310,6 +310,12 @@ defmodule Mobilizon.GraphQL.Schema.UserType do resolve(&User.refresh_token/3) end + @desc "Logout an user, deleting a refresh token" + field :logout, :string do + arg(:refresh_token, non_null(:string)) + resolve(&User.logout/3) + end + @desc "Change default actor for user" field :change_default_actor, :user do arg(:preferred_username, non_null(:string), description: "The actor preferred_username")