diff --git a/js/src/views/User/Validate.vue b/js/src/views/User/Validate.vue
index 7ac0a216e..76b3237d8 100644
--- a/js/src/views/User/Validate.vue
+++ b/js/src/views/User/Validate.vue
@@ -19,9 +19,10 @@
diff --git a/js/src/vue-apollo.ts b/js/src/vue-apollo.ts
index 69b2ef726..770e90b08 100644
--- a/js/src/vue-apollo.ts
+++ b/js/src/vue-apollo.ts
@@ -1,15 +1,18 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { ApolloLink } from 'apollo-link';
+import { ApolloLink, Observable } from 'apollo-link';
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
+import { onError } from 'apollo-link-error';
import { createLink } from 'apollo-absinthe-upload-link';
-import { AUTH_TOKEN } from './constants';
import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH } from './api/_entrypoint';
-import { withClientState } from 'apollo-link-state';
-import { currentUser } from '@/apollo/user';
-import merge from 'lodash/merge';
import { ApolloClient } from 'apollo-client';
import { DollarApollo } from 'vue-apollo/types/vue-apollo';
+import { buildCurrentUserResolver } from '@/apollo/user';
+import { isServerError } from '@/types/apollo';
+import { inspect } from 'util';
+import { REFRESH_TOKEN } from '@/graphql/auth';
+import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN } from '@/constants';
+import { logout, saveTokenData } from '@/utils/auth';
// Install the vue plugin
Vue.use(VueApollo);
@@ -44,14 +47,11 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({
},
});
-const cache = new InMemoryCache({ fragmentMatcher });
-
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
- const token = localStorage.getItem(AUTH_TOKEN);
operation.setContext({
headers: {
- authorization: token ? `Bearer ${token}` : null,
+ authorization: generateTokenHeader(),
},
});
@@ -64,21 +64,54 @@ const uploadLink = createLink({
uri: httpEndpoint,
});
-const stateLink = withClientState({
- ...merge(currentUser),
- cache,
+let refreshingTokenPromise: Promise
| undefined;
+let alreadyRefreshedToken = false;
+const errorLink = onError(({ graphQLErrors, networkError, forward, operation }) => {
+ if (isServerError(networkError) && networkError.statusCode === 401 && !alreadyRefreshedToken) {
+ if (!refreshingTokenPromise) refreshingTokenPromise = refreshAccessToken();
+
+ return promiseToObservable(refreshingTokenPromise).flatMap(() => {
+ refreshingTokenPromise = undefined;
+ alreadyRefreshedToken = true;
+
+ const context = operation.getContext();
+ const oldHeaders = context.headers;
+
+ operation.setContext({
+ headers: {
+ ...oldHeaders,
+ authorization: generateTokenHeader(),
+ },
+ });
+
+ return forward(operation);
+ });
+ }
+
+ if (graphQLErrors) {
+ graphQLErrors.forEach(({ message, locations, path }) =>
+ console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`),
+ );
+ }
+
+ if (networkError) console.log(`[Network error]: ${networkError}`);
});
-const link = stateLink.concat(authMiddleware).concat(uploadLink);
+const link = authMiddleware
+ .concat(errorLink)
+ .concat(uploadLink);
+
+const cache = new InMemoryCache({ fragmentMatcher });
const apolloClient = new ApolloClient({
cache,
link,
connectToDevTools: true,
+ resolvers: {
+ currentUser: buildCurrentUserResolver(cache),
+ },
});
-apolloClient.onResetStore(stateLink.writeDefaults as any);
-
export const apolloProvider = new VueApollo({
defaultClient: apolloClient,
errorHandler(error) {
@@ -93,13 +126,65 @@ export function onLogin(apolloClient) {
}
// Manually call this when user log out
-export async function onLogout(apolloClient: DollarApollo) {
+export async function onLogout() {
// if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient);
try {
- await apolloClient.provider.defaultClient.resetStore();
+ await apolloClient.resetStore();
} catch (e) {
// eslint-disable-next-line no-console
console.log('%cError on cache reset (logout)', 'color: orange;', e.message);
}
}
+
+async function refreshAccessToken() {
+ // Remove invalid access token, so the next request is not authenticated
+ localStorage.removeItem(AUTH_ACCESS_TOKEN);
+
+ const refreshToken = localStorage.getItem(AUTH_REFRESH_TOKEN);
+
+ console.log('Refreshing access token.');
+
+ try {
+ const res = await apolloClient.mutate({
+ mutation: REFRESH_TOKEN,
+ variables: {
+ refreshToken,
+ },
+ });
+
+ saveTokenData(res.data.refreshToken);
+
+ return true;
+ } catch (err) {
+
+ return false;
+ }
+}
+
+function generateTokenHeader() {
+ const token = localStorage.getItem(AUTH_ACCESS_TOKEN);
+
+ return token ? `Bearer ${token}` : null;
+}
+
+// Thanks: https://github.com/apollographql/apollo-link/issues/747#issuecomment-502676676
+const promiseToObservable = (promise: Promise) => {
+ return new Observable((subscriber) => {
+ promise.then(
+ (value) => {
+ if (subscriber.closed) {
+ return;
+ }
+ subscriber.next(value);
+ subscriber.complete();
+ },
+ (err) => {
+ console.error('Cannot refresh token.', err);
+
+ subscriber.error(err);
+ logout(apolloClient);
+ },
+ );
+ });
+};
diff --git a/js/yarn.lock b/js/yarn.lock
index 0d77013df..d449524c3 100644
--- a/js/yarn.lock
+++ b/js/yarn.lock
@@ -1845,6 +1845,15 @@ apollo-link-dedup@^1.0.0:
apollo-link "^1.2.12"
tslib "^1.9.3"
+apollo-link-error@^1.1.11:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.11.tgz#7cd363179616fb90da7866cee85cb00ee45d2f3b"
+ integrity sha512-442DNqn3CNRikDaenMMkoDmCRmkoUx/XyUMlRTZBEFdTw3FYPQLsmDO3hzzC4doY5/BHcn9/jdYh9EeLx4HPsA==
+ dependencies:
+ apollo-link "^1.2.12"
+ apollo-link-http-common "^0.2.14"
+ tslib "^1.9.3"
+
apollo-link-http-common@^0.2.14, apollo-link-http-common@^0.2.4:
version "0.2.14"
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.14.tgz#d3a195c12e00f4e311c417f121181dcc31f7d0c8"
@@ -1863,14 +1872,6 @@ apollo-link-http@^1.3.2, apollo-link-http@^1.5.14:
apollo-link-http-common "^0.2.14"
tslib "^1.9.3"
-apollo-link-state@^0.4.2:
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/apollo-link-state/-/apollo-link-state-0.4.2.tgz#ac00e9be9b0ca89eae0be6ba31fe904b80bbe2e8"
- integrity sha512-xMPcAfuiPVYXaLwC6oJFIZrKgV3GmdO31Ag2eufRoXpvT0AfJZjdaPB4450Nu9TslHRePN9A3quxNueILlQxlw==
- dependencies:
- apollo-utilities "^1.0.8"
- graphql-anywhere "^4.1.0-alpha.0"
-
apollo-link@^1.0.0, apollo-link@^1.0.7, apollo-link@^1.2.11, apollo-link@^1.2.12:
version "1.2.12"
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.12.tgz#014b514fba95f1945c38ad4c216f31bcfee68429"
@@ -1890,7 +1891,7 @@ apollo-utilities@1.2.1:
ts-invariant "^0.2.1"
tslib "^1.9.3"
-apollo-utilities@1.3.2, apollo-utilities@^1.0.8, apollo-utilities@^1.2.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2:
+apollo-utilities@1.3.2, apollo-utilities@^1.2.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9"
integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg==
@@ -4931,15 +4932,6 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
-graphql-anywhere@^4.1.0-alpha.0:
- version "4.2.4"
- resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.2.4.tgz#7f1c08c9348c730c6bb5e818c81f0b72c13696a8"
- integrity sha512-rN6Op5vle0Ucqo8uOVPuFzRz1L/MB+ZVa+XezhFcQ6iP13vy95HOXRysrRtWcu2kQQTLyukSGmfU08D8LXWSIw==
- dependencies:
- apollo-utilities "^1.3.2"
- ts-invariant "^0.3.2"
- tslib "^1.9.3"
-
graphql-tag@^2.10.1:
version "2.10.1"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.1.tgz#10aa41f1cd8fae5373eaf11f1f67260a3cad5e02"
@@ -10196,13 +10188,6 @@ ts-invariant@^0.2.1:
dependencies:
tslib "^1.9.3"
-ts-invariant@^0.3.2:
- version "0.3.3"
- resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.3.tgz#b5742b1885ecf9e29c31a750307480f045ec0b16"
- integrity sha512-UReOKsrJFGC9tUblgSRWo+BsVNbEd77Cl6WiV/XpMlkifXwNIJbknViCucHvVZkXSC/mcWeRnIGdY7uprcwvdQ==
- dependencies:
- tslib "^1.9.3"
-
ts-invariant@^0.4.0:
version "0.4.4"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86"
diff --git a/lib/mobilizon/users/users.ex b/lib/mobilizon/users/users.ex
index 0883f5d75..92697d931 100644
--- a/lib/mobilizon/users/users.ex
+++ b/lib/mobilizon/users/users.ex
@@ -27,7 +27,9 @@ defmodule Mobilizon.Users do
@spec register(map()) :: {:ok, User.t()} | {:error, String.t()}
def register(%{email: _email, password: _password} = args) do
with {:ok, %User{} = user} <-
- %User{} |> User.registration_changeset(args) |> Mobilizon.Repo.insert() do
+ %User{}
+ |> User.registration_changeset(args)
+ |> Mobilizon.Repo.insert() do
Mobilizon.Events.create_feed_token(%{"user_id" => user.id})
{:ok, user}
end
@@ -51,13 +53,15 @@ defmodule Mobilizon.Users do
from(u in User, where: u.email == ^email, preload: :default_actor)
true ->
- from(u in User,
+ from(
+ u in User,
where: u.email == ^email and not is_nil(u.confirmed_at),
preload: :default_actor
)
false ->
- from(u in User,
+ from(
+ u in User,
where: u.email == ^email and is_nil(u.confirmed_at),
preload: :default_actor
)
@@ -75,7 +79,8 @@ defmodule Mobilizon.Users do
@spec get_user_by_activation_token(String.t()) :: Actor.t()
def get_user_by_activation_token(token) do
Repo.one(
- from(u in User,
+ from(
+ u in User,
where: u.confirmation_token == ^token,
preload: [:default_actor]
)
@@ -88,7 +93,8 @@ defmodule Mobilizon.Users do
@spec get_user_by_reset_password_token(String.t()) :: Actor.t()
def get_user_by_reset_password_token(token) do
Repo.one(
- from(u in User,
+ from(
+ u in User,
where: u.reset_password_token == ^token,
preload: [:default_actor]
)
@@ -197,14 +203,16 @@ defmodule Mobilizon.Users do
@spec get_actor_for_user(Mobilizon.Users.User.t()) :: Mobilizon.Actors.Actor.t()
def get_actor_for_user(%Mobilizon.Users.User{} = user) do
case Repo.one(
- from(a in Actor,
+ from(
+ a in Actor,
join: u in User,
on: u.default_actor_id == a.id,
where: u.id == ^user.id
)
) do
nil ->
- case user |> get_actors_for_user() do
+ case user
+ |> get_actors_for_user() do
[] -> nil
actors -> hd(actors)
end
@@ -223,22 +231,55 @@ defmodule Mobilizon.Users do
"""
def authenticate(%{user: user, password: password}) do
# Does password match the one stored in the database?
- case Argon2.verify_pass(password, user.password_hash) do
- true ->
- # Yes, create and return the token
- MobilizonWeb.Guardian.encode_and_sign(user)
-
+ with true <- Argon2.verify_pass(password, user.password_hash),
+ # Yes, create and return the token
+ {:ok, tokens} <- generate_tokens(user) do
+ {:ok, tokens}
+ else
_ ->
# No, return an error
{:error, :unauthorized}
end
end
+ @doc """
+ Generate access token and refresh token
+ """
+ def generate_tokens(user) do
+ with {:ok, access_token} <- generate_access_token(user),
+ {:ok, refresh_token} <- generate_refresh_token(user) do
+ {:ok, %{access_token: access_token, refresh_token: refresh_token}}
+ end
+ end
+
+ defp generate_access_token(user) do
+ with {:ok, access_token, _claims} <-
+ MobilizonWeb.Guardian.encode_and_sign(user, %{}, token_type: "access") do
+ {:ok, access_token}
+ end
+ end
+
+ def generate_refresh_token(user) do
+ with {:ok, refresh_token, _claims} <-
+ MobilizonWeb.Guardian.encode_and_sign(user, %{}, token_type: "refresh") do
+ {:ok, refresh_token}
+ end
+ end
+
def update_user_default_actor(user_id, actor_id) do
with _ <-
- from(u in User, where: u.id == ^user_id, update: [set: [default_actor_id: ^actor_id]])
+ from(
+ u in User,
+ where: u.id == ^user_id,
+ update: [
+ set: [
+ default_actor_id: ^actor_id
+ ]
+ ]
+ )
|> Repo.update_all([]) do
- Repo.get!(User, user_id) |> Repo.preload([:default_actor])
+ Repo.get!(User, user_id)
+ |> Repo.preload([:default_actor])
end
end
diff --git a/lib/mobilizon_web/context.ex b/lib/mobilizon_web/context.ex
index be5834d04..7721162d4 100644
--- a/lib/mobilizon_web/context.ex
+++ b/lib/mobilizon_web/context.ex
@@ -17,7 +17,8 @@ defmodule MobilizonWeb.Context do
context =
case Guardian.Plug.current_resource(conn) do
%User{} = user ->
- Map.put(context, :current_user, user)
+ context
+ |> Map.put(:current_user, user)
nil ->
context
diff --git a/lib/mobilizon_web/guardian.ex b/lib/mobilizon_web/guardian.ex
index 465178f1b..8fd954c6d 100644
--- a/lib/mobilizon_web/guardian.ex
+++ b/lib/mobilizon_web/guardian.ex
@@ -61,6 +61,14 @@ defmodule MobilizonWeb.Guardian do
end
end
+ def on_refresh({old_token, old_claims}, {new_token, new_claims}, _options) do
+ with {:ok, _, _} <- Guardian.DB.on_refresh({old_token, old_claims}, {new_token, new_claims}) do
+ {:ok, {old_token, old_claims}, {new_token, new_claims}}
+ end
+ end
+
+ def on_exchange(old_stuff, new_stuff, options), do: on_refresh(old_stuff, new_stuff, options)
+
# def build_claims(claims, _resource, opts) do
# claims = claims
# |> encode_permissions_into_claims!(Keyword.get(opts, :permissions))
diff --git a/lib/mobilizon_web/resolvers/user.ex b/lib/mobilizon_web/resolvers/user.ex
index fed884d19..500036fa3 100644
--- a/lib/mobilizon_web/resolvers/user.ex
+++ b/lib/mobilizon_web/resolvers/user.ex
@@ -20,7 +20,15 @@ defmodule MobilizonWeb.Resolvers.User do
@doc """
Return current logged-in user
"""
- def get_current_user(_parent, _args, %{context: %{current_user: user}}) do
+ def get_current_user(
+ _parent,
+ _args,
+ %{
+ context: %{
+ current_user: user
+ }
+ }
+ ) do
{:ok, user}
end
@@ -35,7 +43,11 @@ defmodule MobilizonWeb.Resolvers.User do
_parent,
%{page: page, limit: limit, sort: sort, direction: direction},
%{
- context: %{current_user: %User{role: role}}
+ context: %{
+ current_user: %User{
+ role: role
+ }
+ }
}
)
when is_moderator(role) do
@@ -53,8 +65,9 @@ defmodule MobilizonWeb.Resolvers.User do
"""
def login_user(_parent, %{email: email, password: password}, _resolution) do
with {:ok, %User{} = user} <- Users.get_user_by_email(email, true),
- {:ok, token, _} <- Users.authenticate(%{user: user, password: password}) do
- {:ok, %{token: token, user: user}}
+ {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
+ Users.authenticate(%{user: user, password: password}) do
+ {:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
else
{:error, :user_not_found} ->
{:error, "User with email not found"}
@@ -64,6 +77,31 @@ defmodule MobilizonWeb.Resolvers.User do
end
end
+ @doc """
+ Refresh a token
+ """
+ def refresh_token(
+ _parent,
+ %{
+ refresh_token: refresh_token
+ },
+ _context
+ ) do
+ with {:ok, user, _claims} <- MobilizonWeb.Guardian.resource_from_token(refresh_token),
+ {:ok, _old, {exchanged_token, _claims}} <-
+ MobilizonWeb.Guardian.exchange(refresh_token, ["access", "refresh"], "access"),
+ {:ok, refresh_token} <- Users.generate_refresh_token(user) do
+ {:ok, %{access_token: exchanged_token, refresh_token: refresh_token}}
+ else
+ {:error, message} ->
+ Logger.debug("Cannot refresh user token: #{inspect(message)}")
+ {:error, "Cannot refresh the token"}
+ end
+ end
+
+ def refresh_token(_parent, _params, _context),
+ do: {:error, "You need to have an existing token to get a refresh token"}
+
@doc """
Register an user:
- check registrations are enabled
@@ -92,9 +130,14 @@ defmodule MobilizonWeb.Resolvers.User do
with {:check_confirmation_token, {:ok, %User{} = user}} <-
{:check_confirmation_token, Activation.check_confirmation_token(token)},
{:get_actor, actor} <- {:get_actor, Users.get_actor_for_user(user)},
- {:guardian_encode_and_sign, {:ok, token, _}} <-
- {:guardian_encode_and_sign, MobilizonWeb.Guardian.encode_and_sign(user)} do
- {:ok, %{token: token, user: Map.put(user, :default_actor, actor)}}
+ {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
+ Users.generate_tokens(user) do
+ {:ok,
+ %{
+ access_token: access_token,
+ refresh_token: refresh_token,
+ user: Map.put(user, :default_actor, actor)
+ }}
else
err ->
Logger.info("Unable to validate user with token #{token}")
@@ -145,15 +188,22 @@ defmodule MobilizonWeb.Resolvers.User do
def reset_password(_parent, %{password: password, token: token}, _resolution) do
with {:ok, %User{} = user} <-
ResetPassword.check_reset_password_token(password, token),
- {:ok, token, _} <- MobilizonWeb.Guardian.encode_and_sign(user) do
- {:ok, %{token: token, user: user}}
+ {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
+ Users.authenticate(%{user: user, password: password}) do
+ {:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
end
end
@doc "Change an user default actor"
- def change_default_actor(_parent, %{preferred_username: username}, %{
- context: %{current_user: user}
- }) do
+ def change_default_actor(
+ _parent,
+ %{preferred_username: username},
+ %{
+ context: %{
+ current_user: user
+ }
+ }
+ ) do
with %Actor{id: actor_id} <- Actors.get_local_actor_by_name(username),
{:user_actor, true} <-
{:user_actor, actor_id in Enum.map(Users.get_actors_for_user(user), & &1.id)},
diff --git a/lib/mobilizon_web/schema.ex b/lib/mobilizon_web/schema.ex
index 38d223007..c3819ab75 100644
--- a/lib/mobilizon_web/schema.ex
+++ b/lib/mobilizon_web/schema.ex
@@ -31,7 +31,12 @@ defmodule MobilizonWeb.Schema do
@desc "A JWT and the associated user ID"
object :login do
- field(:token, non_null(:string), description: "A JWT Token for this session")
+ field(:access_token, non_null(:string), description: "A JWT Token for this session")
+
+ field(:refresh_token, non_null(:string),
+ description: "A JWT Token to refresh the access token"
+ )
+
field(:user, non_null(:user), description: "The user associated to this session")
end
diff --git a/lib/mobilizon_web/schema/user.ex b/lib/mobilizon_web/schema/user.ex
index 86bbf8d73..1b3a525e6 100644
--- a/lib/mobilizon_web/schema/user.ex
+++ b/lib/mobilizon_web/schema/user.ex
@@ -45,6 +45,12 @@ defmodule MobilizonWeb.Schema.UserType do
)
end
+ @desc "Token"
+ object :refreshed_token do
+ field(:access_token, non_null(:string), description: "Generated access token")
+ field(:refresh_token, non_null(:string), description: "Generated refreshed token")
+ end
+
@desc "Users list"
object :users do
field(:total, non_null(:integer), description: "Total elements")
@@ -118,12 +124,18 @@ defmodule MobilizonWeb.Schema.UserType do
end
@desc "Login an user"
- field :login, :login do
+ field :login, type: :login do
arg(:email, non_null(:string))
arg(:password, non_null(:string))
resolve(&User.login_user/3)
end
+ @desc "Refresh a token"
+ field :refresh_token, type: :refreshed_token do
+ arg(:refresh_token, non_null(:string))
+ resolve(&User.refresh_token/3)
+ end
+
@desc "Change default actor for user"
field :change_default_actor, :user do
arg(:preferred_username, non_null(:string))
diff --git a/mix.exs b/mix.exs
index a3e2c19d8..4357ae930 100644
--- a/mix.exs
+++ b/mix.exs
@@ -7,7 +7,7 @@ defmodule Mobilizon.Mixfile do
[
app: :mobilizon,
version: @version,
- elixir: "~> 1.9",
+ elixir: "~> 1.8",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
start_permanent: Mix.env() == :prod,
diff --git a/test/mobilizon/users/users_test.exs b/test/mobilizon/users/users_test.exs
index a7ebceeb9..bb43805ab 100644
--- a/test/mobilizon/users/users_test.exs
+++ b/test/mobilizon/users/users_test.exs
@@ -68,7 +68,7 @@ defmodule Mobilizon.UsersTest do
test "authenticate/1 checks the user's password" do
{:ok, %User{} = user} = Users.register(%{email: @email, password: @password})
- assert {:ok, _, _} = Users.authenticate(%{user: user, password: @password})
+ assert {:ok, _} = Users.authenticate(%{user: user, password: @password})
assert {:error, :unauthorized} ==
Users.authenticate(%{user: user, password: "bad password"})
diff --git a/test/mobilizon_web/resolvers/user_resolver_test.exs b/test/mobilizon_web/resolvers/user_resolver_test.exs
index dd96bd98c..b996d7ea9 100644
--- a/test/mobilizon_web/resolvers/user_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/user_resolver_test.exs
@@ -3,6 +3,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
alias Mobilizon.{Actors, Users, CommonConfig}
alias Mobilizon.Actors.Actor
alias Mobilizon.Users.User
+ alias Mobilizon.Users
alias MobilizonWeb.AbsintheHelpers
alias Mobilizon.Service.Users.ResetPassword
import Mobilizon.Factory
@@ -433,7 +434,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
validateUser(
token: "#{user.confirmation_token}"
) {
- token,
+ accessToken,
user {
id,
},
@@ -456,7 +457,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
validateUser(
token: "no pass"
) {
- token,
+ accessToken,
user {
id
},
@@ -641,7 +642,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
end
end
- describe "Resolver: Login an user" do
+ describe "Resolver: Login a user" do
test "test login_user/3 with valid credentials", context do
{:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})
@@ -658,7 +659,8 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
email: "#{user.email}",
password: "#{user.password}",
) {
- token,
+ accessToken,
+ refreshToken,
user {
id
}
@@ -671,7 +673,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert login = json_response(res, 200)["data"]["login"]
- assert Map.has_key?(login, "token") && not is_nil(login["token"])
+ assert Map.has_key?(login, "accessToken") && not is_nil(login["accessToken"])
end
test "test login_user/3 with invalid password", context do
@@ -690,7 +692,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
email: "#{user.email}",
password: "bad password",
) {
- token,
+ accessToken,
user {
default_actor {
preferred_username,
@@ -715,7 +717,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
email: "bad email",
password: "bad password",
) {
- token,
+ accessToken,
user {
default_actor {
preferred_username,
@@ -733,6 +735,66 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
end
end
+ describe "Resolver: Refresh a token" do
+ test "test refresh_token/3 with a bad token", context do
+ mutation = """
+ mutation {
+ refreshToken(
+ refreshToken: "bad_token"
+ ) {
+ accessToken
+ }
+ }
+ """
+
+ res =
+ context.conn
+ |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+
+ assert hd(json_response(res, 200)["errors"])["message"] ==
+ "Cannot refresh the token"
+ end
+
+ test "test refresh_token/3 with an appropriate token", context do
+ user = insert(:user)
+ {:ok, refresh_token} = Users.generate_refresh_token(user)
+
+ mutation = """
+ mutation {
+ refreshToken(
+ refreshToken: "#{refresh_token}"
+ ) {
+ accessToken
+ }
+ }
+ """
+
+ res =
+ context.conn
+ |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+
+ assert json_response(res, 200)["errors"] == nil
+
+ access_token = json_response(res, 200)["data"]["refreshToken"]["accessToken"]
+ assert String.length(access_token) > 10
+
+ query = """
+ {
+ loggedPerson {
+ preferredUsername,
+ }
+ }
+ """
+
+ res =
+ context.conn
+ |> Plug.Conn.put_req_header("authorization", "Bearer #{access_token}")
+ |> post("/api", AbsintheHelpers.query_skeleton(query, "logged_person"))
+
+ assert json_response(res, 200)["errors"] == nil
+ end
+ end
+
describe "Resolver: change default actor for user" do
test "test change_default_actor/3 with valid actor", context do
# Prepare user with two actors