Improve JWT tokens expiration

- Reduce access tokens TTL to 15 minutes
- Set refresh tokens TTL to 60 days
- Set Guardian.DB to only track refresh tokens
- Remove refresh token when logging out

Closes #710 #705 #706

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-05-25 11:00:46 +02:00
parent 5a13c2191c
commit a7da5ab269
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
6 changed files with 50 additions and 3 deletions

View file

@ -120,14 +120,19 @@ config :logger, Sentry.LoggerBackend,
level: :warn, level: :warn,
capture_log_messages: true 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, config :guardian, Guardian.DB,
repo: Mobilizon.Storage.Repo, repo: Mobilizon.Storage.Repo,
# default # default
schema_name: "guardian_tokens", schema_name: "guardian_tokens",
# store all token types if not set # store all token types if not set
# token_types: ["refresh_token"], token_types: ["refresh"],
# default: 60 minutes # default: 60 minutes
sweep_interval: 60 sweep_interval: 60

View file

@ -46,3 +46,9 @@ export const REFRESH_TOKEN = gql`
} }
} }
`; `;
export const LOGOUT = gql`
mutation Logout($refreshToken: String!) {
logout(refreshToken: $refreshToken)
}
`;

View file

@ -14,6 +14,7 @@ import { IPerson } from "@/types/actor";
import { IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; import { IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
import { ICurrentUserRole } from "@/types/enums"; import { ICurrentUserRole } from "@/types/enums";
import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types"; import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types";
import { LOGOUT } from "@/graphql/auth";
export function saveTokenData(obj: IToken): void { export function saveTokenData(obj: IToken): void {
localStorage.setItem(AUTH_ACCESS_TOKEN, obj.accessToken); localStorage.setItem(AUTH_ACCESS_TOKEN, obj.accessToken);
@ -96,6 +97,13 @@ export async function initializeCurrentActor(
export async function logout( export async function logout(
apollo: ApolloClient<NormalizedCacheObject> apollo: ApolloClient<NormalizedCacheObject>
): Promise<void> { ): Promise<void> {
await apollo.mutate({
mutation: LOGOUT,
variables: {
refreshToken: localStorage.getItem(AUTH_REFRESH_TOKEN),
},
});
await apollo.mutate({ await apollo.mutate({
mutation: UPDATE_CURRENT_USER_CLIENT, mutation: UPDATE_CURRENT_USER_CLIENT,
variables: { variables: {

View file

@ -385,7 +385,7 @@ export default class Notifications extends Vue {
private async isSubscribed(): Promise<boolean> { private async isSubscribed(): Promise<boolean> {
if (!("serviceWorker" in navigator)) return Promise.resolve(false); if (!("serviceWorker" in navigator)) return Promise.resolve(false);
const registration = await navigator.serviceWorker.getRegistration(); const registration = await navigator.serviceWorker.getRegistration();
return (await registration?.pushManager.getSubscription()) !== null; return (await registration?.pushManager.getSubscription()) != null;
} }
private async deleteFeedToken(token: string): Promise<void> { private async deleteFeedToken(token: string): Promise<void> {

View file

@ -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")} {:error, dgettext("errors", "You need to have an existing token to get a refresh token")}
end 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 """ @doc """
Register an user: Register an user:
- check registrations are enabled - check registrations are enabled

View file

@ -310,6 +310,12 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
resolve(&User.refresh_token/3) resolve(&User.refresh_token/3)
end 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" @desc "Change default actor for user"
field :change_default_actor, :user do field :change_default_actor, :user do
arg(:preferred_username, non_null(:string), description: "The actor preferred_username") arg(:preferred_username, non_null(:string), description: "The actor preferred_username")