| @@ -24,6 +24,7 @@ | |||
| "lodash": "^4.17.21", | |||
| "lodash.isempty": "^4.4.0", | |||
| "owasp-password-strength-test": "^1.3.0", | |||
| "qs": "^6.11.0", | |||
| "react": "^17.0.2", | |||
| "react-dom": "^17.0.2", | |||
| "react-helmet-async": "^1.0.9", | |||
| @@ -8,7 +8,7 @@ import { | |||
| NOT_FOUND_PAGE, | |||
| ERROR_PAGE, | |||
| BASE_PAGE, | |||
| GOOGLE_AUTH_CALLBACK_PAGE, | |||
| AUTH_CALLBACK_PAGE, | |||
| REGISTER_PAGE, | |||
| } from "./constants/pages"; | |||
| @@ -18,7 +18,7 @@ import NotFoundPage from "./pages/ErrorPages/NotFoundPage"; | |||
| import ErrorPage from "./pages/ErrorPages/ErrorPage"; | |||
| import ForgotPasswordPage from "./pages/ForgotPasswordPage/ForgotPasswordPageMUI"; | |||
| import PrivateRoute from "./components/Router/PrivateRoute"; | |||
| import GoogleAuthCallback from "./pages/GoogleAuthCallback/GoogleAuthCallback"; | |||
| import AuthCallback from "./pages/AuthCallback/AuthCallback"; | |||
| import RegisterPage from "./pages/RegisterPage/RegisterPageMUI"; | |||
| const AppRoutes = () => ( | |||
| @@ -26,7 +26,7 @@ const AppRoutes = () => ( | |||
| <Route exact path={BASE_PAGE} component={LoginPage} /> | |||
| <Route exact path={LOGIN_PAGE} component={LoginPage} /> | |||
| <Route exact path={REGISTER_PAGE} component={RegisterPage} /> | |||
| <Route path={GOOGLE_AUTH_CALLBACK_PAGE} component={GoogleAuthCallback} /> | |||
| <Route path={AUTH_CALLBACK_PAGE} component={AuthCallback} /> | |||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | |||
| <Route path={ERROR_PAGE} component={ErrorPage} /> | |||
| <Route path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage} /> | |||
| @@ -5,4 +5,4 @@ export const FORGOT_PASSWORD_PAGE = '/forgot-password'; | |||
| export const HOME_PAGE = '/home'; | |||
| export const ERROR_PAGE = '/error-page'; | |||
| export const NOT_FOUND_PAGE = '/not-found'; | |||
| export const GOOGLE_AUTH_CALLBACK_PAGE = '/api/auth/google/callback' | |||
| export const AUTH_CALLBACK_PAGE = '/api/auth/:provider/callback' | |||
| @@ -0,0 +1,54 @@ | |||
| import React, { useEffect } from "react"; | |||
| import { useLocation } from "react-router-dom"; | |||
| import { HOME_PAGE } from "../../constants/pages"; | |||
| import PropTypes from "prop-types"; | |||
| import { useParams } from "react-router-dom"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { fetchAuthProvider } from "../../store/actions/authProvider/authProviderActions"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import { AUTH_PROVIDER_SCOPE } from "../../store/actions/authProvider/authProviderActionConstants"; | |||
| import Backdrop from '../../components/MUI/BackdropComponent'; | |||
| function AuthCallback({ history }) { | |||
| const dispatch = useDispatch(); | |||
| const { provider } = useParams(); | |||
| const location = useLocation(); | |||
| const handleApiResponseSuccess = () => { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| }; | |||
| const isLoading = useSelector(selectIsLoadingByActionType(AUTH_PROVIDER_SCOPE)); | |||
| useEffect(() => { | |||
| if (!location) { | |||
| return; | |||
| } | |||
| const { search } = location; | |||
| dispatch(fetchAuthProvider({ provider, search, handleApiResponseSuccess })); | |||
| }, [location]); | |||
| return ( | |||
| <div> | |||
| {isLoading && <Backdrop position="absolute" isLoading={isLoading} />} | |||
| </div> | |||
| ); | |||
| } | |||
| AuthCallback.propTypes = { | |||
| history: PropTypes.shape({ | |||
| replace: PropTypes.func, | |||
| push: PropTypes.func, | |||
| location: PropTypes.shape({ | |||
| pathname: PropTypes.string, | |||
| }), | |||
| }), | |||
| }; | |||
| export default AuthCallback; | |||
| @@ -1,60 +0,0 @@ | |||
| import React, { useEffect } from "react"; | |||
| import { useLocation } from "react-router-dom"; | |||
| import { authScopeSetHelper } from "../../util/helpers/authScopeHelpers"; | |||
| import { addHeaderToken } from "../../request"; | |||
| import { HOME_PAGE } from "../../constants/pages"; | |||
| import { JWT_REFRESH_TOKEN, JWT_TOKEN } from "../../constants/localStorage"; | |||
| import { setUser } from "../../store/actions/user/userActions"; | |||
| import PropTypes from "prop-types"; | |||
| import axios from "axios"; | |||
| function GoogleAuthCallback({ history }) { | |||
| const location = useLocation(); | |||
| const handleApiResponseSuccess = () => { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| }; | |||
| useEffect(() => { | |||
| if (!location) { | |||
| return; | |||
| } | |||
| const { search } = location; | |||
| axios({ | |||
| method: "GET", | |||
| url: `http://localhost:1337/api/auth/google/callback?${search}`, | |||
| }).then((res) => { | |||
| if (res.data?.jwt) { | |||
| const user = res.data?.user; | |||
| authScopeSetHelper(JWT_TOKEN, res.data.jwt); | |||
| authScopeSetHelper(JWT_REFRESH_TOKEN, res.data.refreshToken); | |||
| addHeaderToken(res.data?.jwt); | |||
| setUser(user); | |||
| handleApiResponseSuccess(); | |||
| } | |||
| }); | |||
| }, [location]); | |||
| return ( | |||
| <div> | |||
| <></> | |||
| </div> | |||
| ); | |||
| } | |||
| GoogleAuthCallback.propTypes = { | |||
| history: PropTypes.shape({ | |||
| replace: PropTypes.func, | |||
| push: PropTypes.func, | |||
| location: PropTypes.shape({ | |||
| pathname: PropTypes.string, | |||
| }), | |||
| }), | |||
| }; | |||
| export default GoogleAuthCallback; | |||
| @@ -18,6 +18,7 @@ export default { | |||
| authentications: { | |||
| getUsernames: 'authenticate/usernames', | |||
| login: 'api/auth/local', | |||
| authProvider: 'api/auth/{provider}/callback?{search}', | |||
| register: 'api/auth/local/register', | |||
| getUserSecurityQuestion: 'users/username/securityquestion', | |||
| confirmSecurityQuestion: 'authenticate/confirm', | |||
| @@ -1,5 +1,5 @@ | |||
| import axios from "axios"; | |||
| // import queryString from 'qs'; | |||
| import queryString from "qs"; | |||
| const request = axios.create({ | |||
| baseURL: "http://localhost:1337/", | |||
| @@ -7,8 +7,8 @@ const request = axios.create({ | |||
| "Content-Type": "application/json", | |||
| }, | |||
| //withCredentials: true, | |||
| // paramsSerializer: (params) => | |||
| // queryString.stringify(params, { arrayFormat: 'comma' }), | |||
| paramsSerializer: (params) => | |||
| queryString.stringify(params, { arrayFormat: "comma" }), | |||
| }); | |||
| export const getRequest = (url, params = null, options = null) => | |||
| @@ -1,4 +1,4 @@ | |||
| import { getRequest, postRequest } from "./index"; | |||
| import { getRequest, postRequest, replaceInUrl } from "./index"; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const getUsernames = (emailorusername) => | |||
| @@ -9,6 +9,14 @@ export const getUsernames = (emailorusername) => | |||
| export const attemptLogin = (payload) => | |||
| postRequest(apiEndpoints.authentications.login, payload); | |||
| export const attemptAuthProvider = (provider, search) => | |||
| getRequest( | |||
| replaceInUrl(apiEndpoints.authentications.authProvider, { | |||
| provider, | |||
| search, | |||
| }) | |||
| ); | |||
| export const attemptRegister = (payload) => | |||
| postRequest(apiEndpoints.authentications.register, payload); | |||
| @@ -0,0 +1,6 @@ | |||
| import { createErrorType, createFetchType, createSuccessType } from "../actionHelpers"; | |||
| export const AUTH_PROVIDER_SCOPE = 'AUTH_PROVIDER'; | |||
| export const AUTH_PROVIDER_FETCH = createFetchType(AUTH_PROVIDER_SCOPE); | |||
| export const AUTH_PROVIDER_SUCCESS = createSuccessType(AUTH_PROVIDER_SCOPE); | |||
| export const AUTH_PROVIDER_ERROR = createErrorType(AUTH_PROVIDER_SCOPE); | |||
| @@ -0,0 +1,20 @@ | |||
| import { | |||
| AUTH_PROVIDER_ERROR, | |||
| AUTH_PROVIDER_FETCH, | |||
| AUTH_PROVIDER_SUCCESS, | |||
| } from "./authProviderActionConstants"; | |||
| export const fetchAuthProvider = (payload) => ({ | |||
| type: AUTH_PROVIDER_FETCH, | |||
| payload, | |||
| }); | |||
| export const fetchAuthProviderSuccess = (payload) => ({ | |||
| type: AUTH_PROVIDER_SUCCESS, | |||
| payload, | |||
| }); | |||
| export const fetchAuthProviderError = (payload) => ({ | |||
| type: AUTH_PROVIDER_ERROR, | |||
| payload, | |||
| }); | |||
| @@ -0,0 +1,37 @@ | |||
| import createReducer from "../../utils/createReducer"; | |||
| import { | |||
| AUTH_PROVIDER_ERROR, | |||
| AUTH_PROVIDER_SUCCESS, | |||
| } from "../../actions/authProvider/authProviderActionConstants"; | |||
| const initialState = { | |||
| success: "", | |||
| errorMessage: "", | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [AUTH_PROVIDER_SUCCESS]: setProvider, | |||
| [AUTH_PROVIDER_ERROR]: setProviderError, | |||
| }, | |||
| initialState | |||
| ); | |||
| function setProvider(state, action) { | |||
| return { | |||
| ...state, | |||
| successMessage: action.payload, | |||
| }; | |||
| } | |||
| function setProviderError(state, action) { | |||
| return { | |||
| ...state, | |||
| errorMessage: action.payload, | |||
| }; | |||
| } | |||
| @@ -4,6 +4,8 @@ import loadingReducer from './loading/loadingReducer'; | |||
| import userReducer from './user/userReducer'; | |||
| import randomDataReducer from './randomData/randomDataReducer'; | |||
| import registerReducer from './register/registerReducer' | |||
| import auhtProviderReducer from './authProvider/auhtProviderReducer'; | |||
| export default combineReducers({ | |||
| login: loginReducer, | |||
| @@ -11,4 +13,5 @@ export default combineReducers({ | |||
| loading:loadingReducer, | |||
| randomData: randomDataReducer, | |||
| register: registerReducer, | |||
| authProvider: auhtProviderReducer | |||
| }); | |||
| @@ -0,0 +1,48 @@ | |||
| import { all, call, put, takeLatest } from "@redux-saga/core/effects"; | |||
| import { attemptAuthProvider } from "../../request/loginRequest"; | |||
| import { | |||
| fetchUserError, | |||
| fetchUserSuccess, | |||
| } from "../actions/login/loginActions"; | |||
| import { setUser } from "../actions/user/userActions"; | |||
| import { addHeaderToken } from "../../request"; | |||
| import { | |||
| JWT_REFRESH_TOKEN, | |||
| JWT_TOKEN, | |||
| } from "../../constants/localStorage"; | |||
| import { | |||
| authScopeSetHelper, | |||
| } from "../../util/helpers/authScopeHelpers"; | |||
| import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper"; | |||
| import { AUTH_PROVIDER_FETCH } from "../actions/authProvider/authProviderActionConstants"; | |||
| import { fetchAuthProviderError, fetchAuthProviderSuccess } from "../actions/authProvider/authProviderActions"; | |||
| function* fetchAuthProvider({ payload }) { | |||
| try { | |||
| const { data } = yield call(attemptAuthProvider, payload.provider, payload.search); | |||
| if (data?.jwt) { | |||
| const user = data?.user; | |||
| yield call(authScopeSetHelper, JWT_TOKEN, data.jwt); | |||
| yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, data?.refreshToken); | |||
| yield call(addHeaderToken, data?.jwt); | |||
| yield put(setUser(user)); | |||
| } | |||
| yield put(fetchUserSuccess(data)); | |||
| yield put (fetchAuthProviderSuccess('Success')) | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| } | |||
| } catch (e) { | |||
| if (e.response && e.response.data) { | |||
| const errorMessage = yield call(rejectErrorCodeHelper, e); | |||
| yield put(fetchUserError(errorMessage)); | |||
| yield put(fetchAuthProviderError('Error')) | |||
| } | |||
| } | |||
| } | |||
| export default function* authProviderSaga() { | |||
| yield all([ | |||
| takeLatest(AUTH_PROVIDER_FETCH, fetchAuthProvider), | |||
| ]); | |||
| } | |||
| @@ -1,7 +1,8 @@ | |||
| import { all } from "redux-saga/effects"; | |||
| import loginSaga from "./loginSaga"; | |||
| import registerSaga from "./registerSaga"; | |||
| import authProviderSaga from "./authProviderSaga"; | |||
| export default function* rootSaga() { | |||
| yield all([loginSaga(), registerSaga()]); | |||
| yield all([loginSaga(), registerSaga(), authProviderSaga()]); | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| import { createSelector } from 'reselect'; | |||
| const authProviderSelector = (state) => state.authProvider; | |||
| export const selectLoginError = createSelector( | |||
| authProviderSelector, | |||
| (state) => state.errorMessage, | |||
| ); | |||
| @@ -9964,6 +9964,13 @@ qs@6.7.0: | |||
| resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" | |||
| integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== | |||
| qs@^6.11.0: | |||
| version "6.11.0" | |||
| resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" | |||
| integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== | |||
| dependencies: | |||
| side-channel "^1.0.4" | |||
| query-string@^4.1.0: | |||
| version "4.3.4" | |||
| resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" | |||