Authentification JWT sur le backend super-graph #8
@ -0,0 +1,18 @@
|
||||
/* fetchUser */
|
||||
|
||||
variables {
|
||||
"email": ""
|
||||
}
|
||||
|
||||
|
||||
query fetchUser {
|
||||
user(where: {email: {eq: $email}}) {
|
||||
id
|
||||
created_at
|
||||
updated_at
|
||||
email,
|
||||
full_name
|
||||
}
|
||||
}
|
||||
|
||||
|
5
frontend/package-lock.json
generated
5
frontend/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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() {
|
||||
<div className="column is-4 is-offset-4">
|
||||
<div className="box">
|
||||
{
|
||||
currentUser ?
|
||||
<p>Bonjour <span className="has-text-weight-bold">{currentUser.email}</span> !</p> :
|
||||
currentUser && currentUser.full_name ?
|
||||
<p>Bonjour <span className="has-text-weight-bold">{currentUser.full_name}</span> !</p> :
|
||||
<p>Veuillez vous authentifier.</p>
|
||||
}
|
||||
</div>
|
||||
|
@ -7,7 +7,8 @@ export const Config = {
|
||||
oauth2AuthorizeURL: get<string>("oauth2AuthorizeURL", "http://localhost:4444/oauth2/auth"),
|
||||
oauth2TokenURL: get<string>("oauth2TokenURL", "http://localhost:4444/oauth2/token"),
|
||||
oauth2LogoutURL: get<string>("oauth2LogoutURL", "http://localhost:4444/oauth2/sessions/logout"),
|
||||
oauth2PostLogoutRedirectURI: get<string>("oauth2PostLogoutRedirectURI", "http://localhost:8081")
|
||||
oauth2PostLogoutRedirectURI: get<string>("oauth2PostLogoutRedirectURI", "http://localhost:8081"),
|
||||
graphQLEndpoint: get<string>("graphQLEndpoint", "http://localhost:8080/api/v1/graphql")
|
||||
};
|
||||
|
||||
function get<T>(key: string, defaultValue: T):T {
|
||||
|
19
frontend/src/store/actions/profile.ts
Normal file
19
frontend/src/store/actions/profile.ts
Normal file
@ -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 }
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function handleFetchProfileSuccess(state: AuthState, { profile }: fetchProfileSuccessAction): AuthState {
|
||||
return {
|
||||
...state,
|
||||
isAuthenticated: true,
|
||||
currentUser: {
|
||||
...profile,
|
||||
}
|
||||
};
|
||||
};
|
18
frontend/src/store/sagas/init.ts
Normal file
18
frontend/src/store/sagas/init.ts
Normal file
@ -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));
|
||||
}
|
@ -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(),
|
||||
]);
|
||||
}
|
||||
|
37
frontend/src/store/sagas/users.ts
Normal file
37
frontend/src/store/sagas/users.ts
Normal file
@ -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 });
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
export interface User {
|
||||
email: string
|
||||
full_name?: string
|
||||
updated_at?: Date
|
||||
created_at?: Date
|
||||
}
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user