diff --git a/backend/config/allow.list b/backend/config/allow.list
index e69de29..92cf2d2 100644
--- a/backend/config/allow.list
+++ b/backend/config/allow.list
@@ -0,0 +1,18 @@
+/* fetchUser */
+
+variables {
+ "email": ""
+}
+
+
+ query fetchUser {
+ user(where: {email: {eq: $email}}) {
+ id
+ created_at
+ updated_at
+ email,
+ full_name
+ }
+ }
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 24c785f..1e597da 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -5499,6 +5499,11 @@
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
+ "graphql-request": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-2.0.0.tgz",
+ "integrity": "sha512-Ww3Ax+G3l2d+mPT8w7HC9LfrKjutnCKtnDq7ZZp2ghVk5IQDjwAk3/arRF1ix17Ky15rm0hrSKVKxRhIVlSuoQ=="
+ },
"handle-thing": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 8dfffd3..6f0d883 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -54,6 +54,7 @@
"@types/qs": "^6.9.3",
"bulma": "^0.7.2",
"bulma-switch": "^2.0.0",
+ "graphql-request": "^2.0.0",
"jwt-decode": "^2.2.0",
"qs": "^6.9.4",
"react": "^16.12.0",
diff --git a/frontend/src/components/HomePage/HomePage.tsx b/frontend/src/components/HomePage/HomePage.tsx
index 150726a..b9ac584 100644
--- a/frontend/src/components/HomePage/HomePage.tsx
+++ b/frontend/src/components/HomePage/HomePage.tsx
@@ -1,6 +1,6 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { Page } from '../Page';
-import { useSelector } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../../store/reducers/root';
export function HomePage() {
@@ -14,8 +14,8 @@ export function HomePage() {
{
- currentUser ?
-
Bonjour {currentUser.email} !
:
+ currentUser && currentUser.full_name ?
+
Bonjour {currentUser.full_name} !
:
Veuillez vous authentifier.
}
diff --git a/frontend/src/config.ts b/frontend/src/config.ts
index 9b59e24..98ee0a4 100644
--- a/frontend/src/config.ts
+++ b/frontend/src/config.ts
@@ -7,7 +7,8 @@ export const Config = {
oauth2AuthorizeURL: get
("oauth2AuthorizeURL", "http://localhost:4444/oauth2/auth"),
oauth2TokenURL: get("oauth2TokenURL", "http://localhost:4444/oauth2/token"),
oauth2LogoutURL: get("oauth2LogoutURL", "http://localhost:4444/oauth2/sessions/logout"),
- oauth2PostLogoutRedirectURI: get("oauth2PostLogoutRedirectURI", "http://localhost:8081")
+ oauth2PostLogoutRedirectURI: get("oauth2PostLogoutRedirectURI", "http://localhost:8081"),
+ graphQLEndpoint: get("graphQLEndpoint", "http://localhost:8080/api/v1/graphql")
};
function get(key: string, defaultValue: T):T {
diff --git a/frontend/src/store/actions/profile.ts b/frontend/src/store/actions/profile.ts
new file mode 100644
index 0000000..c46a75f
--- /dev/null
+++ b/frontend/src/store/actions/profile.ts
@@ -0,0 +1,19 @@
+import { Action } from "redux";
+import { User } from "../../types/user";
+
+export const FETCH_PROFILE_REQUEST = 'FETCH_PROFILE_REQUEST';
+export const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
+export const FETCH_PROFILE_FAILURE = 'FETCH_PROFILE_FAILURE';
+
+export interface fetchProfileRequestAction extends Action {
+
+}
+
+export interface fetchProfileSuccessAction extends Action {
+ profile: User
+}
+
+
+export function fetchProfile(): fetchProfileRequestAction {
+ return { type: FETCH_PROFILE_REQUEST }
+}
\ No newline at end of file
diff --git a/frontend/src/store/reducers/auth.ts b/frontend/src/store/reducers/auth.ts
index a0bd507..e888748 100644
--- a/frontend/src/store/reducers/auth.ts
+++ b/frontend/src/store/reducers/auth.ts
@@ -1,6 +1,7 @@
import { Action } from "redux";
import { User } from "../../types/user";
import { SET_CURRENT_USER, setCurrentUserAction, LOGOUT } from "../actions/auth";
+import { FETCH_PROFILE_SUCCESS, fetchProfileSuccessAction } from "../actions/profile";
export interface AuthState {
isAuthenticated: boolean
@@ -18,6 +19,9 @@ export function authReducer(state = defaultState, action: Action): AuthState {
return handleSetCurrentUser(state, action as setCurrentUserAction);
case LOGOUT:
return handleLogout(state);
+ case FETCH_PROFILE_SUCCESS:
+ return handleFetchProfileSuccess(state, action as fetchProfileSuccessAction);
+
}
return state;
}
@@ -38,4 +42,14 @@ function handleLogout(state: AuthState): AuthState {
isAuthenticated: false,
currentUser: null,
};
-}
\ No newline at end of file
+};
+
+function handleFetchProfileSuccess(state: AuthState, { profile }: fetchProfileSuccessAction): AuthState {
+ return {
+ ...state,
+ isAuthenticated: true,
+ currentUser: {
+ ...profile,
+ }
+ };
+};
\ No newline at end of file
diff --git a/frontend/src/store/sagas/init.ts b/frontend/src/store/sagas/init.ts
new file mode 100644
index 0000000..da4d595
--- /dev/null
+++ b/frontend/src/store/sagas/init.ts
@@ -0,0 +1,18 @@
+import { all, put } from "redux-saga/effects";
+import { getSavedAccessGrant } from "../../util/auth";
+import { parseIdToken } from "../actions/auth";
+
+export function* initRootSaga() {
+ yield all([
+ retrieveSessionSaga(),
+ ]);
+}
+
+export function* retrieveSessionSaga() {
+ console.log("Checking session status...");
+
+ const accessGrant = getSavedAccessGrant();
+ if (!accessGrant) return;
+
+ yield put(parseIdToken(accessGrant.id_token));
+}
\ No newline at end of file
diff --git a/frontend/src/store/sagas/root.ts b/frontend/src/store/sagas/root.ts
index dfa853a..71fdf80 100644
--- a/frontend/src/store/sagas/root.ts
+++ b/frontend/src/store/sagas/root.ts
@@ -1,10 +1,14 @@
import { all } from 'redux-saga/effects';
import { failureRootSaga } from './failure';
import { authRootSaga } from './auth';
+import { initRootSaga } from './init';
+import { usersRootSaga } from './users';
export function* rootSaga() {
yield all([
+ initRootSaga(),
failureRootSaga(),
authRootSaga(),
+ usersRootSaga(),
]);
}
diff --git a/frontend/src/store/sagas/users.ts b/frontend/src/store/sagas/users.ts
new file mode 100644
index 0000000..40521b3
--- /dev/null
+++ b/frontend/src/store/sagas/users.ts
@@ -0,0 +1,37 @@
+import { DaddyClient } from "../../util/daddy";
+import { Config } from "../../config";
+import { getSavedAccessGrant } from "../../util/auth";
+import { all, takeLatest, put, select } from "redux-saga/effects";
+import { FETCH_PROFILE_REQUEST, fetchProfile, FETCH_PROFILE_FAILURE, FETCH_PROFILE_SUCCESS } from "../actions/profile";
+import { SET_CURRENT_USER } from "../actions/auth";
+import { RootState } from "../reducers/root";
+import { User } from "../../types/user";
+
+export function* usersRootSaga() {
+ yield all([
+ takeLatest(SET_CURRENT_USER, onCurrentUserChangeSaga),
+ takeLatest(FETCH_PROFILE_REQUEST, fetchProfileSaga),
+ ]);
+}
+
+export function* onCurrentUserChangeSaga() {
+ yield put(fetchProfile());
+}
+
+
+
+export function* fetchProfileSaga() {
+ const grant = getSavedAccessGrant();
+ const client = new DaddyClient(Config.graphQLEndpoint, grant.id_token);
+
+ let profile: User;
+ try {
+ const currentUser: User = yield select((state: RootState) => state.auth.currentUser);
+ profile = yield client.fetchUser(currentUser.email).then(result => result.user);
+ } catch(err) {
+ yield put({ type: FETCH_PROFILE_FAILURE, err });
+ return;
+ }
+
+ yield put({type: FETCH_PROFILE_SUCCESS, profile });
+}
\ No newline at end of file
diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts
index 70d7e8b..13c920d 100644
--- a/frontend/src/types/user.ts
+++ b/frontend/src/types/user.ts
@@ -1,3 +1,6 @@
export interface User {
email: string
+ full_name?: string
+ updated_at?: Date
+ created_at?: Date
}
\ No newline at end of file
diff --git a/frontend/src/util/daddy.ts b/frontend/src/util/daddy.ts
index 22bb4c5..953ea9d 100644
--- a/frontend/src/util/daddy.ts
+++ b/frontend/src/util/daddy.ts
@@ -1,3 +1,6 @@
+import { GraphQLClient } from 'graphql-request'
+import { Config } from "../config";
+
export class UnauthorizedError extends Error {
constructor(...args: any[]) {
super(...args)
@@ -7,16 +10,35 @@ export class UnauthorizedError extends Error {
export class DaddyClient {
- assertOk(res: any) {
- if (!res.ok) return Promise.reject(new Error('Request failed'));
- return res;
+ gql: GraphQLClient
+
+ constructor(endpoint: string, idToken: string) {
+ this.gql = new GraphQLClient(endpoint, {
+ headers: {
+ Authorization: `Bearer ${idToken}`,
+ mode: 'cors',
+ }
+ });
}
- assertAuthorization(res: any) {
- if (res.status === 401 || res.status === 404) return Promise.reject(new UnauthorizedError());
- return res;
+ fetchUser(email: string) {
+ return this.gql.rawRequest(`
+ query fetchUser {
+ user(where: {email: {eq: $email}}) {
+ id
+ created_at
+ updated_at
+ email,
+ full_name
+ }
+ }
+ `, { email })
+ .then(this.assertAuthorization)
}
-}
+ assertAuthorization({ status, data }: any) {
+ if (status === 401) return Promise.reject(new UnauthorizedError());
+ return data;
+ }
-export const daddy = new DaddyClient();
\ No newline at end of file
+}
\ No newline at end of file