├── .gitignore
├── LICENSE
├── README.md
├── email-templates
├── emailverification.html
└── forgotPasswordEmail.html
├── frontend
├── .gitlab-ci.yml
├── Jenkinsfile
├── README.md
├── cicd
│ └── test.sh
├── package.json
├── public
│ ├── assets
│ │ ├── images
│ │ │ ├── brand
│ │ │ │ ├── apple-touch-icon-76x76.png
│ │ │ │ └── favicon-32x32.png
│ │ │ └── flags
│ │ │ │ ├── en.png
│ │ │ │ └── es.png
│ │ └── img
│ │ │ └── brand
│ │ │ ├── apple-touch-icon-76x76.png
│ │ │ └── favicon-32x32.png
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
│ ├── assets
│ ├── css
│ │ └── custom.css
│ └── img
│ │ └── brand
│ │ └── appIcon.png
│ ├── components
│ ├── App
│ │ ├── App.jsx
│ │ ├── App.test.jsx
│ │ └── index.jsx
│ ├── CustomButton
│ │ ├── CustomButton.jsx
│ │ └── index.jsx
│ ├── Form
│ │ └── Input
│ │ │ ├── Input.jsx
│ │ │ ├── InputAddon.jsx
│ │ │ ├── InputCheckbox.jsx
│ │ │ ├── InputError.jsx
│ │ │ ├── InputLabel.jsx
│ │ │ ├── InputText.jsx
│ │ │ └── index.jsx
│ ├── NavDropdown
│ │ ├── GuestNavDropdown
│ │ │ ├── GuestLinks.json
│ │ │ ├── GuestNavDropdown.jsx
│ │ │ └── index.jsx
│ │ ├── LanguageDropdown
│ │ │ ├── LanguageDropdown.jsx
│ │ │ └── index.jsx
│ │ └── MemberNavDropdown
│ │ │ ├── MemberLinks.json
│ │ │ ├── MemberNavDropdown.jsx
│ │ │ └── index.jsx
│ ├── Navbar
│ │ ├── Navbar.jsx
│ │ └── index.jsx
│ └── Util
│ │ ├── Authenticating
│ │ ├── Authenticating.jsx
│ │ └── index.jsx
│ │ ├── Loading
│ │ ├── Loading.jsx
│ │ └── index.jsx
│ │ ├── NotAuthorized
│ │ ├── NotAuthorized.jsx
│ │ └── index.jsx
│ │ └── PageData
│ │ ├── PageData.jsx
│ │ └── index.jsx
│ ├── config
│ ├── config.jsx
│ ├── index.jsx
│ └── links.jsx
│ ├── data
│ └── language
│ │ ├── en
│ │ └── common.json
│ │ ├── es
│ │ └── common.json
│ │ └── languages.json
│ ├── index.js
│ ├── layouts
│ ├── DashLayout.jsx
│ └── index.jsx
│ ├── redux
│ ├── actionTypes
│ │ ├── actionTypes.jsx
│ │ └── index.jsx
│ ├── actions
│ │ ├── index.jsx
│ │ ├── language.jsx
│ │ └── user.jsx
│ ├── reducers
│ │ ├── index.jsx
│ │ ├── languageReducer.jsx
│ │ ├── rootReducer.jsx
│ │ └── userReducer.jsx
│ └── store
│ │ ├── index.jsx
│ │ └── store.jsx
│ ├── routes.js
│ ├── serviceWorker.js
│ ├── util
│ ├── APIFetch
│ │ ├── APIFetch.jsx
│ │ └── index.jsx
│ ├── Auth
│ │ ├── Auth.jsx
│ │ ├── AuthAPI.jsx
│ │ └── index.jsx
│ ├── FormHandler
│ │ ├── FormHandler.jsx
│ │ └── index.jsx
│ ├── History
│ │ ├── History.jsx
│ │ └── index.jsx
│ ├── Language
│ │ ├── Language.jsx
│ │ └── index.jsx
│ ├── Logout
│ │ ├── Logout.jsx
│ │ ├── LogoutAPI.jsx
│ │ └── index.jsx
│ ├── PrivateRoute
│ │ ├── PrivateRoute.jsx
│ │ └── index.jsx
│ ├── ResponseHandler
│ │ ├── ResponseHandler.jsx
│ │ └── index.jsx
│ ├── Toasty
│ │ ├── Toasty.jsx
│ │ └── index.jsx
│ └── ValidateInput
│ │ ├── ValidateInput.jsx
│ │ └── index.jsx
│ └── views
│ ├── Auth
│ ├── ChangePassword
│ │ ├── ChangePassword.jsx
│ │ ├── ChangePasswordAPI.jsx
│ │ ├── ChangePasswordController.jsx
│ │ ├── ChangePasswordForm.json
│ │ └── index.jsx
│ ├── ForgotPassword
│ │ ├── ForgotPassword.jsx
│ │ ├── ForgotPasswordAPI.jsx
│ │ ├── ForgotPasswordController.jsx
│ │ ├── ForgotPasswordForm.json
│ │ └── index.jsx
│ ├── Login
│ │ ├── Login.jsx
│ │ ├── LoginAPI.jsx
│ │ ├── LoginController.jsx
│ │ ├── LoginForm.json
│ │ └── index.jsx
│ ├── Register
│ │ ├── Register.jsx
│ │ ├── RegisterAPI.jsx
│ │ ├── RegisterForm.json
│ │ └── index.jsx
│ └── Verify
│ │ ├── Verify2Factor
│ │ ├── Verify2Factor.jsx
│ │ ├── Verify2FactorAPI.jsx
│ │ ├── Verify2FactorController.jsx
│ │ ├── Verify2FactorForm.json
│ │ └── index.jsx
│ │ ├── VerifyEmail
│ │ ├── VerifyEmail.jsx
│ │ ├── VerifyEmailAPI.jsx
│ │ ├── VerifyEmailForm.json
│ │ ├── VerifyEmailPageData.jsx
│ │ └── index.jsx
│ │ └── index.jsx
│ ├── Home.jsx
│ ├── NotFound
│ ├── NotFound.jsx
│ └── index.jsx
│ ├── Todo
│ ├── TodoAPI.jsx
│ ├── TodoList
│ │ ├── TodoList.jsx
│ │ ├── TodoListPageData.jsx
│ │ └── index.jsx
│ ├── TodoModify
│ │ ├── TodoModify.jsx
│ │ ├── TodoModifyForm.json
│ │ ├── TodoModifyPageData.jsx
│ │ └── index.jsx
│ ├── TodoView
│ │ ├── TodoView.jsx
│ │ ├── TodoViewPageData.jsx
│ │ └── index.jsx
│ └── index.jsx
│ └── User
│ ├── User2Factor
│ ├── Disable
│ │ ├── Disable.jsx
│ │ ├── Disable2FAAPI.jsx
│ │ ├── Disable2FAController.jsx
│ │ ├── Disable2FAForm.json
│ │ └── index.jsx
│ ├── Enable
│ │ ├── Enable.jsx
│ │ ├── Enable2FAAPI.jsx
│ │ ├── Enable2FAController.jsx
│ │ ├── Enable2FAForm.json
│ │ └── index.jsx
│ ├── User2Factor.jsx
│ ├── User2FactorAPI.jsx
│ └── index.jsx
│ ├── UserChangePassword
│ ├── UserChangePassword.jsx
│ ├── UserChangePasswordAPI.jsx
│ ├── UserChangePasswordController.jsx
│ ├── UserChangePasswordForm.json
│ └── index.jsx
│ ├── UserProfile
│ ├── UserProfileEdit
│ │ ├── UserProfileEdit.jsx
│ │ ├── UserProfileEditAPI.jsx
│ │ ├── UserProfileEditController.jsx
│ │ ├── UserProfileEditForm.json
│ │ ├── UserProfileEditPageData.jsx
│ │ └── index.jsx
│ ├── UserProfileView
│ │ ├── UserProfileView.jsx
│ │ ├── UserProfileViewAPI.jsx
│ │ ├── UserProfileViewPageData.jsx
│ │ └── index.jsx
│ └── index.jsx
│ └── index.jsx
├── mongodb
└── roles.json
└── server
├── .dockerignore
├── .drone.yaml
├── .example.env
├── .gitlab-ci.yml
├── Dockerfile
├── Jenkinsfile
├── README.md
├── package-lock.json
├── package.json
├── server.js
└── src
├── components
├── fusionAuth
│ ├── fusionAuth.js
│ ├── fusionAuthAPI.js
│ ├── fusionAuthInputs.json
│ └── index.js
├── health
│ ├── health.js
│ └── index.js
├── roles
│ ├── index.js
│ ├── roles.js
│ └── rolesModel.js
├── todo
│ ├── index.js
│ ├── todo.js
│ ├── todoInputs.json
│ └── todoModel.js
└── user
│ ├── index.js
│ ├── user.js
│ ├── userAPI.js
│ └── userInputs.json
├── config
├── config.js
└── index.js
├── data
├── fusionAuth
│ ├── fusionAuthData.js
│ └── index.js
└── language
│ ├── en
│ ├── common.json
│ └── index.js
│ └── es
│ ├── common.json
│ └── index.js
├── database
├── database.js
└── index.js
├── expressServer
├── expressAPI.js
├── expressMiddleware.js
├── expressRoutes.js
├── expressServer.js
└── index.js
└── util
├── apiFetch
├── apiFetch.js
└── index.js
├── auth
├── auth.js
└── index.js
├── language
├── index.js
└── language.js
└── validForm
├── index.js
└── validForm.js
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/build
3 | **/npm-debug.log
4 | *.DS_Store
5 | server/public
6 | **/package-lock.json
7 | .env
8 | **/.gitignore
--------------------------------------------------------------------------------
/frontend/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: node:latest
2 |
3 | stages:
4 | - install
5 | - test
6 | - build
7 | - deploy
8 |
9 | variables:
10 | AWS_DEFAULT_REGION: us-east-2
11 | BUCKET_NAME: fusionauth-demo
12 | ENV_PROD: https://fusionauth-demo.domain.com
13 | CLOUDFRONT_ID: some-id
14 |
15 | install:
16 | stage: install
17 | script:
18 | - npm ci --only=production
19 | artifacts:
20 | paths:
21 | - node_modules/
22 | cache:
23 | key: node
24 | paths:
25 | - node_modules/
26 |
27 | test:
28 | stage: test
29 | dependencies:
30 | - install
31 | script:
32 | - chmod +x ./cicd/test.sh
33 | - ./cicd/test.sh
34 |
35 | build:
36 | stage: build
37 | dependencies:
38 | - install
39 | script:
40 | - npm run build
41 | artifacts:
42 | paths:
43 | - build/
44 | cache:
45 | key: build
46 | paths:
47 | - build/
48 |
49 | deploy_prod:
50 | image: "python:latest"
51 | stage: deploy
52 | dependencies:
53 | - build
54 | before_script:
55 | - pip install awscli
56 | script:
57 | - aws s3 sync --cache-control 'max-age=604800' --delete dist/ s3://${BUCKET_NAME}
58 | - aws cloudfront create-invalidation --distribution-id ${CLOUDFRONT_ID} --paths "/*"
59 | environment:
60 | name: Production
61 | url: ${ENV_PROD}
62 | when: manual
63 | only:
64 | - master
--------------------------------------------------------------------------------
/frontend/Jenkinsfile:
--------------------------------------------------------------------------------
1 | podTemplate(label: "mypod", containers: [
2 | containerTemplate(name: "node", image: "node", ttyEnabled: true, command: "cat"),
3 | ]) {
4 | node("mypod") {
5 | def uatBucket = "fusionauth-demo"
6 | def prodBucket = "fusionauth-demo"
7 |
8 | stage ("Checkout Code") {
9 | checkout scm
10 | }
11 |
12 | container("node") {
13 | stage("Install Packages") {
14 | sh "npm install"
15 | }
16 |
17 | stage("Test and Build") {
18 | parallel(
19 | "Run Tests": {
20 | sh "chmod +x ./cicd/test.sh"
21 | sh "./cicd/test.sh"
22 | },
23 | "Create Build Artifacts": {
24 | sh "npm run build"
25 | }
26 | )
27 | }
28 |
29 | if (env.BRANCH_NAME == "master") {
30 | stage("Deploy - Production") {
31 | withAWS(region:"us-east-2", credentials:"credentials-id") {
32 | s3Delete(bucket: "${prodBucket}", path:"/")
33 | s3Upload(bucket: "${prodBucket}", workingDir:"build", includePathPattern:"**/*", cacheControl:"public,max-age=604800", excludePathPattern:"**/index.html");
34 | s3Upload(bucket: "${prodBucket}", workingDir:"build", includePathPattern:"**/index.html", cacheControl:"no-cache");
35 | }
36 | }
37 | } else {
38 | stage("Deploy - Staging") {
39 | withAWS(region:"us-east-2", credentials:"credentials-id") {
40 | s3Delete(bucket: "${uatBucket}", path:"/")
41 | s3Upload(bucket: "${uatBucket}", workingDir:"build", includePathPattern:"**/*", cacheControl:"public,max-age=604800", excludePathPattern:"**/index.html");
42 | s3Upload(bucket: "${uatBucket}", workingDir:"build", includePathPattern:"**/index.html", cacheControl:"no-cache");
43 | }
44 | }
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # FusionAuth React Example
2 |
3 | This is the React portion of the FusionAuth NodeJS + React Todo Example.
4 |
5 | ## About
6 | The React application uses React Redux for state storage for information about the user and language data, though there are more optimal solutions for larger applications for language state. Font awesome is used for icons in the application and axios is used for making requests to FusionAuth or the API server. All inputs are validated through a custom validation module prior to submitting any API request.
7 |
8 | ## Prerequisites
9 | * Have already installed dependencies with `npm install`.
10 |
11 | ## Configuration
12 |
13 | Configuration is done in `src/config/config.jsx`. Expressed in dot notation below.
14 |
15 | | Parameter | Description | Example |
16 | | ------------- | ------------- | ----- |
17 | | **FusionAuth** |
18 | | fusionAuth.BASEURL | The URL where FusionAuth is hosted and set up. | `http://localhost:9011` |
19 | | fusionAuth.APPLICATION_ID | The generated application ID for the app created in the FusionAuth client for the demo. | `10e4d908-7655-44af-abf0-1a031aff519a` |
20 | | **API Server** |
21 | | apiServer.BASEURL | The URL where the API server is hosted and set up. | `http://localhost:5000` |
22 | | **React** |
23 | | app.TWO_FA_NAME | A name to be given to React Redux for state storage in the browser. | `fusionAuthDemoApp` |
--------------------------------------------------------------------------------
/frontend/cicd/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | echo "The following "npm" command tests that your simple Node.js/React"
4 | echo "application renders satisfactorily. This command actually invokes the test"
5 | echo "runner Jest (https://facebook.github.io/jest/)."
6 | set -x
7 | npm test
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fusionauth-nodejs-react-example",
3 | "description": "An example application using NodeJS, ExpressJS, ReactJS and FusionAuth",
4 | "version": "1.0.0",
5 | "author": "FusionAuth",
6 | "license": "Apache-2.0",
7 | "private": true,
8 | "scripts": {
9 | "start": "react-scripts start",
10 | "build": "react-scripts build",
11 | "test": "cross-env CI=true react-scripts test",
12 | "eject": "react-scripts eject"
13 | },
14 | "contributors": [
15 | {
16 | "name": "Tyler Engelhardt",
17 | "url": "https://mainelysoftware.com",
18 | "email": "tyler@mainelysoftware.com"
19 | }
20 | ],
21 | "eslintConfig": {
22 | "extends": "react-app"
23 | },
24 | "bugs": {
25 | "url": "https://github.com/FusionAuth/fusionauth-nodejs-react-example/issues"
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/FusionAuth/fusionauth-nodejs-react-example"
30 | },
31 | "dependencies": {
32 | "@fortawesome/fontawesome-svg-core": "^1.2.22",
33 | "@fortawesome/free-solid-svg-icons": "^5.10.2",
34 | "@fortawesome/react-fontawesome": "^0.1.4",
35 | "axios": "^0.19.0",
36 | "bootstrap": "^4.3.1",
37 | "cross-env": "^5.2.1",
38 | "lodash": "^4.17.15",
39 | "qrcode.react": "^0.9.3",
40 | "react": "^16.9.0",
41 | "react-dom": "^16.9.0",
42 | "react-redux": "^7.1.1",
43 | "react-router-dom": "^5.0.1",
44 | "react-scripts": "3.0.1",
45 | "react-telephone-input": "^4.73.4",
46 | "react-toastify": "^5.3.2",
47 | "reactstrap": "^8.0.0",
48 | "redux": "^4.0.4",
49 | "redux-persist": "^6.0.0",
50 | "redux-thunk": "^2.3.0"
51 | },
52 | "browserslist": {
53 | "production": [
54 | ">0.2%",
55 | "not dead",
56 | "not op_mini all"
57 | ],
58 | "development": [
59 | "last 1 chrome version",
60 | "last 1 firefox version",
61 | "last 1 safari version"
62 | ]
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/frontend/public/assets/images/brand/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FusionAuth/fusionauth-nodejs-react-example/b0a2ba366c1d7c4fbcd507a296a0845d072221a6/frontend/public/assets/images/brand/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/frontend/public/assets/images/brand/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FusionAuth/fusionauth-nodejs-react-example/b0a2ba366c1d7c4fbcd507a296a0845d072221a6/frontend/public/assets/images/brand/favicon-32x32.png
--------------------------------------------------------------------------------
/frontend/public/assets/images/flags/en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FusionAuth/fusionauth-nodejs-react-example/b0a2ba366c1d7c4fbcd507a296a0845d072221a6/frontend/public/assets/images/flags/en.png
--------------------------------------------------------------------------------
/frontend/public/assets/images/flags/es.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FusionAuth/fusionauth-nodejs-react-example/b0a2ba366c1d7c4fbcd507a296a0845d072221a6/frontend/public/assets/images/flags/es.png
--------------------------------------------------------------------------------
/frontend/public/assets/img/brand/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FusionAuth/fusionauth-nodejs-react-example/b0a2ba366c1d7c4fbcd507a296a0845d072221a6/frontend/public/assets/img/brand/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/frontend/public/assets/img/brand/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FusionAuth/fusionauth-nodejs-react-example/b0a2ba366c1d7c4fbcd507a296a0845d072221a6/frontend/public/assets/img/brand/favicon-32x32.png
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FusionAuth/fusionauth-nodejs-react-example/b0a2ba366c1d7c4fbcd507a296a0845d072221a6/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
17 | FusionAuth - NodeJS & ReactJS Example
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "FusionAuth Example",
3 | "name": "FusionAuth - NodeJS & ReactJS Example",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
--------------------------------------------------------------------------------
/frontend/src/assets/img/brand/appIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FusionAuth/fusionauth-nodejs-react-example/b0a2ba366c1d7c4fbcd507a296a0845d072221a6/frontend/src/assets/img/brand/appIcon.png
--------------------------------------------------------------------------------
/frontend/src/components/App/App.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { Router, Route, Switch } from "react-router-dom";
4 | import { ToastContainer } from "react-toastify";
5 | import { Provider } from "react-redux";
6 | import { PersistGate } from "redux-persist/integration/react";
7 | import { library } from "@fortawesome/fontawesome-svg-core";
8 | import {
9 | faTimes,
10 | faKey,
11 | faPowerOff,
12 | faUser,
13 | faUserSecret,
14 | faUserNinja,
15 | faUserTie,
16 | faUserPlus,
17 | faEnvelope,
18 | faUserLock,
19 | faUserShield,
20 | faLock,
21 | faSync,
22 | faPhone,
23 | faClipboardList,
24 | faCheck,
25 | faTasks,
26 | faFeatherAlt,
27 | faCode,
28 | faShieldAlt
29 | } from "@fortawesome/free-solid-svg-icons";
30 |
31 | // Styling
32 | import "react-toastify/dist/ReactToastify.min.css";
33 | import "bootstrap/dist/css/bootstrap.min.css";
34 | import "../../assets/css/custom.css";
35 |
36 | // History
37 | import History from "../../util/History";
38 |
39 | // Layouts
40 | import DashLayout from "../../layouts/DashLayout";
41 |
42 | // Redux
43 | import { store, persistor } from "../../redux/store";
44 |
45 | // Font Awesome Initialization
46 | library.add(
47 | faTimes,
48 | faKey,
49 | faPowerOff,
50 | faUser,
51 | faUserSecret,
52 | faUserNinja,
53 | faUserTie,
54 | faUserPlus,
55 | faEnvelope,
56 | faUserLock,
57 | faUserShield,
58 | faLock,
59 | faSync,
60 | faPhone,
61 | faClipboardList,
62 | faCheck,
63 | faTasks,
64 | faFeatherAlt,
65 | faCode,
66 | faShieldAlt
67 | );
68 |
69 | /**
70 | * Application Component
71 | *
72 | * This is where we declare the application. We use BrowserRouter to encapsulate
73 | * the application for routing purposes, and then we use Switch to be able to
74 | * handle the different routes.
75 | */
76 | const App = () => {
77 | return (
78 |
79 |
80 |
81 |
82 |
83 | } />
84 |
85 |
86 |
87 |
88 | );
89 | };
90 |
91 | // Export the Application Component.
92 | export default App;
--------------------------------------------------------------------------------
/frontend/src/components/App/App.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
--------------------------------------------------------------------------------
/frontend/src/components/App/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import App from "./App";
3 |
4 | // Export modules
5 | export default App;
--------------------------------------------------------------------------------
/frontend/src/components/CustomButton/CustomButton.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { Button } from "reactstrap";
4 |
5 | /**
6 | * Custom Button Component
7 | *
8 | * @param {String} color Button color
9 | * @param {String} text Button text
10 | * @param {String} type Button type
11 | * @param {Boolean} disabled Button disabled status
12 | * @param {String} className Button classes
13 | * @param {Function} onClick onClick function handler
14 | *
15 | */
16 | const CustomButton = ({ color, text, type, disabled, className, onClick }) => (
17 |
20 | );
21 |
22 | // Export the Custom Button component.
23 | export default CustomButton;
--------------------------------------------------------------------------------
/frontend/src/components/CustomButton/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import CustomButton from "./CustomButton";
3 |
4 | // Export modules
5 | export default CustomButton;
--------------------------------------------------------------------------------
/frontend/src/components/Form/Input/Input.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Input Component
3 | *
4 | * References the different types of inputs available in order to display
5 | * them properly.
6 | */
7 |
8 | // Dependencies
9 | import React from "react";
10 |
11 | // Components
12 | import InputCheckbox from "./InputCheckbox";
13 | //import InputFile from "./InputFile";
14 | import InputText from "./InputText";
15 |
16 | // Available types of inputs
17 | const availableInputs = {
18 | checkbox: InputCheckbox,
19 | //file: InputFile,
20 | text: InputText
21 | };
22 |
23 | /**
24 | * Input Component
25 | *
26 | * Custom input component that displays the proper custom elements if they
27 | * exist and are defined above.
28 | *
29 | * @param {Object} props Properties from the parent function.
30 | */
31 | const Input = props => {
32 | // Get the right value for the input based on its type.
33 | const inputValue = props.type === "checkbox" || props.type === "toggle"
34 | ? props.value || false
35 | : props.value || "";
36 |
37 | // Determine if the Custom input should be displayed, or the InputDNE.
38 | const Input = availableInputs[props.type] ? availableInputs[props.type] : availableInputs["text"];
39 |
40 | // Return the Custom Input.
41 | return ;
42 | }
43 |
44 | // Export the Custom Input component.
45 | export default Input;
--------------------------------------------------------------------------------
/frontend/src/components/Form/Input/InputAddon.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { isEmpty } from "lodash";
4 | import { InputGroupAddon, InputGroupText } from "reactstrap";
5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6 |
7 | /**
8 | * Custom Input Addon
9 | *
10 | * Displays a custom input addon for form inputs. Can be used for prepend or append
11 | * addons.
12 | *
13 | * @param {Object} type Type of addon (prepend || append).
14 | * @param {Object} icon Icon to be displayed.
15 | */
16 | const InputAddon = ({ type, icon }) => {
17 | return !isEmpty(type) && !isEmpty(icon)
18 | ?
19 |
20 |
21 |
22 |
23 | : "";
24 | }
25 |
26 | // Export the Custom Input Addon component.
27 | export default InputAddon;
28 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/Input/InputCheckbox.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { isFunction } from "lodash";
4 | import { Col, FormGroup, FormFeedback, Input, FormText } from "reactstrap";
5 |
6 | // Components
7 | import InputLabel from "./InputLabel";
8 | import InputError from "./InputError";
9 |
10 | // Utils
11 | import ValidateInput from "../../../util/ValidateInput";
12 |
13 | /**
14 | * Input Checkbox Component
15 | *
16 | * Custom input checkbox component that displays the input and related components for
17 | * the input depending on the information supplied.
18 | *
19 | * @param {Object} props Properties from the parent function.
20 | */
21 | const InputCheckbox = props => {
22 | // Properties for the input display.
23 | const {
24 | inputColXL,
25 | inputColMD,
26 | inputColClassName,
27 | id,
28 | autoFocus,
29 | disabled,
30 | formGroupClassName,
31 | inputClassName,
32 | inputCheckboxClassName,
33 | inputStyle,
34 | label,
35 | labelClassName,
36 | labelMuted,
37 | name,
38 | validate,
39 | onFocus,
40 | onChange,
41 | tabIndex,
42 | value,
43 | success,
44 | error,
45 | formHelpText,
46 | } = props;
47 |
48 | /**
49 | * Handle input validation
50 | *
51 | * Validate inputs on blur.
52 | *
53 | * @param {Object} target The input that fired the event.
54 | */
55 | const handleValidate = ({ target }) => {
56 | validate(target.name, ValidateInput(value, target.type, props.validation, props.languageData));
57 | };
58 |
59 | // Display the input and related content.
60 | return (
61 |
62 |
63 |
77 |
78 | { success }
79 |
80 | { formHelpText }
81 |
82 |
83 | );
84 | }
85 |
86 | // Export the Custom Input Checkbox component.
87 | export default InputCheckbox;
88 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/Input/InputError.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React, { useState, useEffect } from "react";
3 | import { isEmpty } from "lodash";
4 | import { FormFeedback} from "reactstrap";
5 |
6 | /**
7 | * Input Error Component
8 | *
9 | * Custom error display for inputs that converts the array of errors into
10 | * a single set of list items.
11 | *
12 | * @param {Array} errors Array of errors for the input item.
13 | */
14 | const InputError = ({ errors }) => {
15 | // Set and get input errors with React Hook.
16 | const [inputErrors, setInputErrors] = useState([]);
17 |
18 | // Use `useEffect` to set and update the errors as they change.
19 | useEffect(() => {
20 | /**
21 | * Update the errors for the input item.
22 | */
23 | const updateErrors = () => {
24 | // If the error array is empty, set the error state to an empty string.
25 | if (isEmpty(errors)) {
26 | setInputErrors("");
27 | } else {
28 | // Set an empty array for the error state.
29 | let listErrors = [];
30 |
31 | // Loop through each error in the passed array and push an `li` item to the error state.
32 | errors.forEach((e, key) => listErrors.push({ e }));
33 |
34 | // Set the error state.
35 | setInputErrors(listErrors);
36 | }
37 | }
38 |
39 | // Call the error update function.
40 | updateErrors();
41 | }, [errors]);
42 |
43 | // Display the error(s).
44 | return { inputErrors };
45 | }
46 |
47 | // Export the Custom Input Error component.
48 | export default InputError;
49 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/Input/InputLabel.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { isEmpty } from "lodash";
4 | import { Label } from "reactstrap";
5 |
6 | /**
7 | * Custom Input Label
8 | *
9 | * Displays a custom input label for form inputs.
10 | *
11 | * @param {Object} props Properties from the parent function.
12 | */
13 | const InputLabel = props => {
14 | return !isEmpty(props.htmlFor) && !isEmpty(props.label)
15 | ?
23 | : "";
24 | }
25 |
26 | // Export the Custom Input Label component.
27 | export default InputLabel;
28 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/Input/InputText.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { isFunction } from "lodash";
4 | import { Col, FormGroup, InputGroup, FormFeedback, Input, FormText } from "reactstrap";
5 |
6 | // Components
7 | import InputLabel from "./InputLabel";
8 | import InputAddon from "./InputAddon";
9 | import InputError from "./InputError";
10 |
11 | // Utils
12 | import ValidateInput from "../../../util/ValidateInput";
13 |
14 | /**
15 | * Input Text Component
16 | *
17 | * Custom input text component that displays the input and related components for
18 | * the input depending on the information supplied. Text is used for all inputs,
19 | * excluding file and checkbox.
20 | *
21 | * @param {Object} props Properties from the parent function.
22 | */
23 | const InputText = props => {
24 | // Properties for the input display.
25 | const {
26 | inputColXL,
27 | inputColMD,
28 | inputColClassName,
29 | id,
30 | type,
31 | autoFocus,
32 | disabled,
33 | inputClassName,
34 | label,
35 | labelClassName,
36 | labelMuted,
37 | name,
38 | value,
39 | rows,
40 | placeholder,
41 | autoComplete,
42 | validate,
43 | secondValue,
44 | onChange,
45 | handleFormSubmit,
46 | onFocus,
47 | tabIndex,
48 | prependIcon,
49 | appendIcon,
50 | success,
51 | error,
52 | formHelpText,
53 | pattern,
54 | maxLength
55 | } = props;
56 |
57 | /**
58 | * Handle input validation
59 | *
60 | * Validate inputs on blur.
61 | *
62 | * @param {Object} target The input that fired the event.
63 | */
64 | const handleValidate = ({ target }) => {
65 | validate(target.name, ValidateInput(target.value, target.type, props.validation, props.languageData, secondValue));
66 | };
67 |
68 | /**
69 | * Handle Key Press
70 | *
71 | * Handles key presses for the input. Work around for some inputs that
72 | * try to refresh the page for an unknown reason. Instead of allowing the
73 | * page to be refreshed, it attempts to submit the form.
74 | *
75 | * @param {Object} e Input object that executed the function.
76 | */
77 | const handleKeyPress = e => {
78 | e.key === "Enter" && isFunction(handleFormSubmit) && handleFormSubmit(e);
79 | };
80 |
81 | // Display the input and related content.
82 | return (
83 |
84 |
85 |
86 |
87 |
88 | { /* Temporary fix for preventing the form arbitrarily submitting when it shouldn't */ }
89 | { /* Handled by onKeyPress */ }
90 |
109 |
110 | { success }
111 |
112 |
113 | { formHelpText }
114 |
115 |
116 | );
117 | }
118 |
119 | // Export the Custom Input Text component.
120 | export default InputText;
121 |
--------------------------------------------------------------------------------
/frontend/src/components/Form/Input/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Input from "./Input";
3 |
4 | // Export modules
5 | export default Input;
--------------------------------------------------------------------------------
/frontend/src/components/NavDropdown/GuestNavDropdown/GuestLinks.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "icon": "key",
4 | "text": [
5 | "common",
6 | "login"
7 | ],
8 | "link": [
9 | "auth",
10 | "login"
11 | ]
12 | },
13 | {
14 | "icon": "user-plus",
15 | "text": [
16 | "common",
17 | "register"
18 | ],
19 | "link": [
20 | "auth",
21 | "register"
22 | ]
23 | }
24 | ]
--------------------------------------------------------------------------------
/frontend/src/components/NavDropdown/GuestNavDropdown/GuestNavDropdown.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { Link } from "react-router-dom";
4 | import { get, map } from "lodash";
5 | import { connect } from "react-redux";
6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7 | import {
8 | DropdownMenu,
9 | DropdownItem
10 | } from "reactstrap";
11 |
12 | // Application Links
13 | import { links } from "../../../config";
14 | import guestLinks from "./GuestLinks";
15 |
16 | /**
17 | * Guest Navbar Component
18 | *
19 | * Contains the display components and associated functions for the guest
20 | * dropdown for the main navbar.
21 | *
22 | * @param {Object} languageData Current language information for the app. Language data object.
23 | */
24 | const GuestNavDropdown = ({ languageData }) => (
25 |
26 |
27 | { get(languageData, ["common", "welcome"]) }
28 |
29 | {
30 | map(guestLinks, (link, key) => (
31 |
32 |
33 | { get(languageData, link["text"]) }
34 |
35 | ))
36 | }
37 |
38 | );
39 |
40 | /**
41 | * Get App State
42 | *
43 | * Get the requried state for the component from the Redux store.
44 | *
45 | * @param {Object} state Application state from Redux.
46 | */
47 | const mapStateToProps = state => {
48 | return {
49 | languageData: state.language.languageData
50 | }
51 | }
52 |
53 | // Export the Guest Navbar Component.
54 | export default connect(mapStateToProps)(GuestNavDropdown);
--------------------------------------------------------------------------------
/frontend/src/components/NavDropdown/GuestNavDropdown/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import GuestNavDropdown from "./GuestNavDropdown";
3 |
4 | // Export modules
5 | export default GuestNavDropdown;
--------------------------------------------------------------------------------
/frontend/src/components/NavDropdown/LanguageDropdown/LanguageDropdown.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React, { useEffect, useRef } from "react";
3 | import { get, map } from "lodash";
4 | import { connect } from "react-redux";
5 | import {
6 | UncontrolledDropdown,
7 | DropdownToggle,
8 | DropdownMenu,
9 | DropdownItem,
10 | Media
11 | } from "reactstrap";
12 |
13 | // Toasty
14 | import Toasty from "../../../util/Toasty";
15 |
16 | // Language
17 | import { setLanguage } from "../../../redux/actions";
18 | import languages from "../../../data/language/languages.json";
19 |
20 | /**
21 | * Language Dropdown Component
22 | *
23 | * Contains the display components and associated functions for the guest
24 | * dropdown for the main navbar.
25 | *
26 | * @param {Function} setLanguage Redux action to change the app's language.
27 | * @param {Object} languageData Current language information for the app. Language data object.
28 | */
29 | const LanguageDropdown = ({ setLanguage, languageData }) => {
30 | // Setup an initial mount reference so we can send Toast notifications
31 | // on update of the languageData.
32 | const initialMount = useRef(true);
33 |
34 | /**
35 | * Listen for updates
36 | *
37 | * Send the user a notification that their language preference was saved.
38 | */
39 | useEffect(() => {
40 | /**
41 | * Notify Function
42 | *
43 | * Notify the user.
44 | */
45 | const doNotify = () => {
46 | // Make sure we don't send a notification if this is the inital render.
47 | if (initialMount.current) {
48 | initialMount.current = false;
49 | } else {
50 | // Display a toast notification that the language was empty.
51 | Toasty.notify({
52 | type: Toasty.info(),
53 | content: get(languageData, ["common", "changeLang", "saved"])
54 | });
55 | }
56 | };
57 |
58 |
59 | // Call the notificaiton function.
60 | doNotify();
61 | }, [languageData]);
62 |
63 | // Display the Language Dropdown.
64 | return (
65 |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 | { get(languageData, ["common", "langData", "name"]) }
77 |
78 |
79 |
80 |
81 |
82 | {
83 | map(languages, (lang, key) => (
84 |
85 |
86 |
91 |
92 | { get(lang, "name") }
93 |
94 | ))
95 | }
96 |
97 |
98 | );
99 | };
100 |
101 | /**
102 | * Get App State
103 | *
104 | * Get the requried state for the component from the Redux store.
105 | *
106 | * @param {Object} state Application state from Redux.
107 | */
108 | const mapStateToProps = state => {
109 | return {
110 | languageData: state.language.languageData
111 | }
112 | }
113 |
114 | // Export the Language Dropdown Component.
115 | export default connect(mapStateToProps, { setLanguage })(LanguageDropdown);
--------------------------------------------------------------------------------
/frontend/src/components/NavDropdown/LanguageDropdown/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import LanguageDropdown from "./LanguageDropdown";
3 |
4 | // Export modules
5 | export default LanguageDropdown;
--------------------------------------------------------------------------------
/frontend/src/components/NavDropdown/MemberNavDropdown/MemberLinks.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "icon": "clipboard-list",
4 | "text": [
5 | "common",
6 | "todoList"
7 | ],
8 | "link": [
9 | "todo",
10 | "list"
11 | ]
12 | },
13 | {
14 | "icon": "user-tie",
15 | "text": [
16 | "common",
17 | "profile"
18 | ],
19 | "link": [
20 | "user",
21 | "profile"
22 | ]
23 | },
24 | {
25 | "icon": "user-shield",
26 | "text": [
27 | "common",
28 | "twoFactorSettings"
29 | ],
30 | "link": [
31 | "user",
32 | "twoFactor"
33 | ]
34 | },
35 | {
36 | "icon": "user-lock",
37 | "text": [
38 | "common",
39 | "changePassword"
40 | ],
41 | "link": [
42 | "user",
43 | "changePassword"
44 | ]
45 | },
46 | {
47 | "icon": "power-off",
48 | "logout": true,
49 | "text": [
50 | "common",
51 | "logout"
52 | ]
53 | }
54 | ]
--------------------------------------------------------------------------------
/frontend/src/components/NavDropdown/MemberNavDropdown/MemberNavDropdown.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { Link } from "react-router-dom";
4 | import { get, map } from "lodash";
5 | import { connect } from "react-redux";
6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7 | import {
8 | DropdownMenu,
9 | DropdownItem
10 | } from "reactstrap";
11 |
12 | // Application Links
13 | import { links } from "../../../config";
14 | import memberLinks from "./MemberLinks.json"
15 |
16 | // Redux Actions
17 | import { logoutUser } from "../../../redux/actions";
18 |
19 | // Toast
20 | import Toasty from "../../../util/Toasty";
21 |
22 | // History
23 | import History from "../../../util/History";
24 |
25 | // Controllers
26 | import LogoutController from "../../../util/Logout";
27 |
28 | /**
29 | * Member Navbar Component
30 | *
31 | * Contains the display components and associated functions for the member
32 | * dropdown for the main navbar.
33 | *
34 | * @param {Function} logoutUser Redux action to unset the user.
35 | * @param {String} locale The current locale of the application.
36 | * @param {Object} languageData Current language information for the app. Language data object.
37 | */
38 | const MemberNavDropdown = ({ logoutUser, locale, languageData }) => {
39 | /**
40 | * Handle logout
41 | *
42 | * Will handle the logout request for when the button is clicked.
43 | *
44 | * @param {object} e Event object
45 | */
46 | const logout = e => {
47 | // Prevent the link from executing.
48 | e.preventDefault();
49 |
50 | // Use the LogoutController to handle the logout.
51 | LogoutController.logout(locale, languageData)
52 | .then(() => {
53 | // Call the logout function.
54 | logoutUser();
55 |
56 | // Redirect the user to the home page.
57 | History.push(links.home);
58 | }).catch(error =>
59 | // Display the error from the API server on logout.
60 | Toasty.notify({
61 | type: Toasty.error(),
62 | content: error
63 | })
64 | );
65 | }
66 |
67 | // Display the Member Nav Dropdown.
68 | return (
69 |
70 |
71 | { get(languageData, ["common", "welcome"]) }
72 |
73 | {
74 | map(memberLinks, (link, key) => (
75 |
76 |
77 | { get(languageData, link["text"]) }
78 |
79 | ))
80 | }
81 |
82 | );
83 | };
84 |
85 | /**
86 | * Get App State
87 | *
88 | * Get the requried state for the component from the Redux store.
89 | *
90 | * @param {Object} state Application state from Redux.
91 | */
92 | const mapStateToProps = state => {
93 | return {
94 | locale: state.language.locale,
95 | languageData: state.language.languageData
96 | }
97 | }
98 |
99 | // Export the Member Navbar Component.
100 | export default connect(mapStateToProps, { logoutUser })(MemberNavDropdown);
--------------------------------------------------------------------------------
/frontend/src/components/NavDropdown/MemberNavDropdown/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import MemberNavDropdown from "./MemberNavDropdown";
3 |
4 | // Export modules
5 | export default MemberNavDropdown;
--------------------------------------------------------------------------------
/frontend/src/components/Navbar/Navbar.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React, { useState } from "react";
3 | import { get } from "lodash";
4 | import { connect } from "react-redux";
5 | import { Link } from "react-router-dom";
6 | import {
7 | Collapse,
8 | NavbarBrand,
9 | Navbar,
10 | Nav,
11 | UncontrolledDropdown,
12 | DropdownToggle,
13 | Media,
14 | Container,
15 | Row,
16 | Col
17 | } from "reactstrap";
18 |
19 | // Icons
20 | import appIcon from "../../assets/img/brand/appIcon.png";
21 |
22 | // Components
23 | import LanguageDropdown from "../NavDropdown/LanguageDropdown";
24 | import GuestNavDropdown from "../NavDropdown/GuestNavDropdown";
25 | import MemberNavDropdown from "../NavDropdown/MemberNavDropdown";
26 |
27 | /**
28 | * Navbar Component
29 | *
30 | * The component includes the Navbar component and functions for the
31 | * Navbar at the top of the page.
32 | *
33 | * @param {Object} languageData Current language information for the app. Language data object.
34 | * @param {Object} user User data for the logged in user.
35 | * @param {object} props Properties passed to the component from the parent.
36 | */
37 | const DashNavbar = ({ languageData, user, ...props}) => {
38 | // React hook used to determine whether or not the menu should be collapsed
39 | // or shown to the user.
40 | const [collapseOpen, setCollapse] = useState(false);
41 |
42 | /**
43 | * Toggle menu collapse
44 | *
45 | * Toggles collapse between opened and closed (true/false).
46 | */
47 | const toggleCollapse = () => {
48 | setCollapse(!collapseOpen);
49 | };
50 |
51 | // Display the Navbar.
52 | return (
53 |
54 |
55 |
62 |
63 | { get(languageData, ["common", "siteTitle"]) }
64 |
65 |
66 |
67 |
68 |
69 |
77 |
78 |
79 |
80 |
109 |
110 |
111 |
112 | );
113 | };
114 |
115 | /**
116 | * Get App State
117 | *
118 | * Get the requried state for the component from the Redux store.
119 | *
120 | * @param {Object} state Application state from Redux.
121 | */
122 | const mapStateToProps = state => {
123 | return {
124 | languageData: state.language.languageData,
125 | user: state.user.info
126 | }
127 | }
128 |
129 | // Export the Navbar Component.
130 | export default connect(mapStateToProps)(DashNavbar);
131 |
--------------------------------------------------------------------------------
/frontend/src/components/Navbar/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Navbar from "./Navbar";
3 |
4 | // Export modules
5 | export default Navbar;
--------------------------------------------------------------------------------
/frontend/src/components/Util/Authenticating/Authenticating.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { get } from "lodash";
4 | import { connect } from "react-redux";
5 | import {
6 | Card,
7 | CardHeader,
8 | CardBody,
9 | Container,
10 | Row,
11 | Col
12 | } from "reactstrap";
13 |
14 | /**
15 | * Authenticating Component
16 | *
17 | * Display a message to the user, in their preferred language, that they
18 | * are being authenticated to access the information.
19 | *
20 | * @param {Object} languageData Current language information for the app. Language data object.
21 | */
22 | const Authenticating = ({ languageData }) => (
23 |
24 |
25 |
26 |
27 |
28 | { get(languageData, ["common", "security"]) }
29 |
30 |
31 | { get(languageData, ["common", "authenticating"]) }...
32 |
33 |
34 |
35 |
36 |
37 | );
38 |
39 | /**
40 | * Get App State
41 | *
42 | * Get the requried state for the component from the Redux store.
43 | *
44 | * @param {Object} state Application state from Redux.
45 | */
46 | const mapStateToProps = state => {
47 | return {
48 | languageData: state.language.languageData
49 | }
50 | }
51 |
52 | // Export the Loading Component.
53 | export default connect(mapStateToProps)(Authenticating);
--------------------------------------------------------------------------------
/frontend/src/components/Util/Authenticating/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Authenticating from "./Authenticating";
3 |
4 | // Export modules
5 | export default Authenticating;
--------------------------------------------------------------------------------
/frontend/src/components/Util/Loading/Loading.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 |
5 | /**
6 | * Loading Component
7 | *
8 | * A simple Font Awesome spinning icon to indicate to the user that
9 | * content is loading for them.
10 | */
11 | const Loading = ({ size = "3x" }) => (
12 |
13 | );
14 |
15 | // Export the Loading Component.
16 | export default Loading;
17 |
--------------------------------------------------------------------------------
/frontend/src/components/Util/Loading/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Loading from "./Loading";
3 |
4 | // Export modules
5 | export default Loading;
--------------------------------------------------------------------------------
/frontend/src/components/Util/NotAuthorized/NotAuthorized.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { get } from "lodash";
4 | import { connect } from "react-redux";
5 | import {
6 | Card,
7 | CardHeader,
8 | CardBody,
9 | Container,
10 | Row,
11 | Col
12 | } from "reactstrap";
13 |
14 | /**
15 | * Authenticating Component
16 | *
17 | * Display a message to the user, in their preferred language, that
18 | * they do not have access to the content.
19 | *
20 | * @param {Object} languageData Current language information for the app. Language data object.
21 | */
22 | const NotAuthorized = ({ languageData }) => (
23 |
24 |
25 |
26 |
27 |
28 | { get(languageData, ["common", "notAuthorized", "title"]) }
29 |
30 |
31 | { get(languageData, ["common", "notAuthorized", "message"]) }
32 |
33 |
34 |
35 |
36 |
37 | );
38 |
39 | /**
40 | * Get App State
41 | *
42 | * Get the requried state for the component from the Redux store.
43 | *
44 | * @param {Object} state Application state from Redux.
45 | */
46 | const mapStateToProps = state => {
47 | return {
48 | languageData: state.language.languageData
49 | }
50 | }
51 |
52 | // Export the Loading Component.
53 | export default connect(mapStateToProps)(NotAuthorized);
54 |
--------------------------------------------------------------------------------
/frontend/src/components/Util/NotAuthorized/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import NotAuthorized from "./NotAuthorized";
3 |
4 | // Export modules
5 | export default NotAuthorized;
--------------------------------------------------------------------------------
/frontend/src/components/Util/PageData/PageData.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { get } from "lodash";
4 |
5 | // Components
6 | import Loading from "../../../components/Util/Loading";
7 |
8 | /**
9 | * Page Data Component
10 | *
11 | * Display page data for different pages with the ability to display a loading
12 | * icon during the loading phase, an error message, or the component intended to
13 | * be displayed.
14 | *
15 | * @param {Object} results Page results
16 | * @param {Boolean} isLoading Loading indication for API request.
17 | * @param {Object} hasError Error object from the API request.
18 | * @param {Object} component Component to be displayed.
19 | */
20 | const PageData = ({ results, isLoading, hasError, component }) => {
21 | // Display errors or the loading message.
22 | const loadingOrErrors = hasError ? <>{ hasError.data.message }> : ;
23 |
24 | // Check if the results are anything but 200 to display their error message.
25 | const isResults200 = get(results, "status") === 200 ? component : <>{ get(results, "data") && results.data.message }>;
26 |
27 | // Return loading, errors, or the page component.
28 | return isLoading ? loadingOrErrors : isResults200;
29 | }
30 |
31 | // Export the User Profile Page Data Component.
32 | export default PageData;
--------------------------------------------------------------------------------
/frontend/src/components/Util/PageData/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import PageData from "./PageData";
3 |
4 | // Export modules
5 | export default PageData;
--------------------------------------------------------------------------------
/frontend/src/config/config.jsx:
--------------------------------------------------------------------------------
1 | // Config object
2 | const config = {
3 | fusionAuth: {
4 | // FusionAuth URL: http://localhost:9011
5 | BASEURL: "http://localhost:9011",
6 | // Application ID from FusionAuth
7 | APPLICATION_ID: "10e4d908-7655-44af-abf0-1a031aff519a"
8 | },
9 | apiServer: {
10 | // API Server URL: http://localhost:5000
11 | BASEURL: "http://localhost:5000"
12 | },
13 | app: {
14 | // Name for the 2Factor application: FusionAuth
15 | TWO_FA_NAME: "fusionAuthDemoApp"
16 | }
17 | };
18 |
19 | // Export the application config.
20 | export default config;
--------------------------------------------------------------------------------
/frontend/src/config/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import config from "./config";
3 | import links from "./links";
4 |
5 | // Export modules
6 | export {
7 | config,
8 | links
9 | };
--------------------------------------------------------------------------------
/frontend/src/config/links.jsx:
--------------------------------------------------------------------------------
1 | // Export the application links.
2 | export default {
3 | home: "/",
4 | auth: {
5 | login: "/auth/login/",
6 | register: "/auth/register/",
7 | verifyEmail: "/auth/verify/email/",
8 | twoFactor: "/auth/verify/2fa/",
9 | forgotPassword: "/auth/forgotPassword/",
10 | changePassword: "/auth/changePassword/"
11 | },
12 | user: {
13 | profile: "/user/profile/",
14 | editProfile: "/user/profile/edit/",
15 | changePassword: "/user/changePassword/",
16 | twoFactor: "/user/2fa/"
17 | },
18 | todo: {
19 | list: "/todo/",
20 | add: "/todo/add/",
21 | edit: "/todo/edit/"
22 | }
23 | };
--------------------------------------------------------------------------------
/frontend/src/data/language/languages.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "English",
4 | "shortcode": "en",
5 | "flag": "/assets/images/flags/en.png"
6 | },
7 | {
8 | "name": "Español",
9 | "shortcode": "es",
10 | "flag": "/assets/images/flags/es.png"
11 | }
12 | ]
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import ReactDOM from "react-dom";
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | // Components
7 | import App from "./components/App";
8 |
9 | // Render the application to the page.
10 | ReactDOM.render(, document.getElementById("root"));
11 |
12 | // If you want your app to work offline and load faster, you can change
13 | // unregister() to register() below. Note this comes with some pitfalls.
14 | // Learn more about service workers: https://bit.ly/CRA-PWA
15 | serviceWorker.unregister();
16 |
--------------------------------------------------------------------------------
/frontend/src/layouts/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import DashLayout from "./DashLayout";
3 |
4 | // Export modules
5 | export default DashLayout;
--------------------------------------------------------------------------------
/frontend/src/redux/actionTypes/actionTypes.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Redux Action Types
3 | *
4 | * Available action types for Redux dispatch operations
5 | * for the application.
6 | */
7 |
8 | // Language
9 | const SET_LANGUAGE = "SET_LANGUAGE";
10 |
11 | // User
12 | const SET_USER = "SET_USER";
13 | const LOGOUT_USER = "LOGOUT_USER";
14 |
15 | // Eexport the action types.
16 | export {
17 | SET_LANGUAGE,
18 | SET_USER,
19 | LOGOUT_USER
20 | };
--------------------------------------------------------------------------------
/frontend/src/redux/actionTypes/index.jsx:
--------------------------------------------------------------------------------
1 | // Import action types.
2 | import {
3 | SET_LANGUAGE,
4 | SET_USER,
5 | LOGOUT_USER
6 | } from "./actionTypes";
7 |
8 | // Export the action types.
9 | export {
10 | SET_LANGUAGE,
11 | SET_USER,
12 | LOGOUT_USER
13 | };
--------------------------------------------------------------------------------
/frontend/src/redux/actions/index.jsx:
--------------------------------------------------------------------------------
1 | // Import actions.
2 | import { setLanguage } from "./language";
3 | import { setUser, logoutUser } from "./user";
4 |
5 | // Export actions.
6 | export {
7 | setLanguage,
8 | setUser,
9 | logoutUser
10 | };
--------------------------------------------------------------------------------
/frontend/src/redux/actions/language.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Language Actions
3 | *
4 | * Action creators for Language based actions.
5 | */
6 |
7 | // Dependencies
8 | import { get } from "lodash";
9 |
10 | // Language
11 | import Language from "../../util/Language";
12 |
13 | // Toasty
14 | import Toasty from "../../util/Toasty";
15 |
16 | // Action Types
17 | import { SET_LANGUAGE } from "../actionTypes";
18 |
19 | /**
20 | * Set the App Language
21 | *
22 | * Performs a dispatch to the Redux store with the new language and
23 | * the language data. If a new language cannot be found, alert the user.
24 | * There's a very small percent chance that no HTML element would be clicked
25 | * that does not have the `data-lang` attribute.
26 | *
27 | * @param {Object} target The HTML element that
28 | */
29 | const setLanguage = ({ target }) => (dispatch, getState) => {
30 | // Get the language attribute from the button and the span item.
31 | // With this setup, it's done this way so that either could be clicked.
32 | // If not, clicking on the text in the dropdown would result in an error.
33 | const newLanguage = target.getAttribute("data-lang");
34 |
35 | // Make sure the language supplied to the function is not null / empty.
36 | if (!newLanguage) {
37 | // Get the language data.
38 | const languageData = getState("language").language.languageData;
39 |
40 | // Display a toast notification that the language was empty.
41 | Toasty.notify({
42 | type: Toasty.error(),
43 | content: get(languageData, ["common", "changeLang", "choose"])
44 | });
45 |
46 | return;
47 | }
48 |
49 | // Get the language data for the new language.
50 | const languageData = Language.getLanguageData(newLanguage);
51 |
52 | // Dispatch the result.
53 | dispatch({
54 | type: SET_LANGUAGE,
55 | payload: {
56 | locale: newLanguage,
57 | languageData
58 | }
59 | });
60 | }
61 |
62 | // Export the actions.
63 | export {
64 | setLanguage
65 | }
--------------------------------------------------------------------------------
/frontend/src/redux/actions/user.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * User Actions
3 | *
4 | * Action creators for User based actions.
5 | */
6 |
7 | // Dependencies
8 | import { get } from "lodash";
9 |
10 | // Toasty
11 | import Toasty from "../../util/Toasty";
12 |
13 | // Action Types
14 | import { SET_USER, LOGOUT_USER } from "../actionTypes";
15 |
16 | /**
17 | * Set the User Object
18 | *
19 | * Attempt to set the user object with data about the user.
20 | *
21 | * @param {Object} user The user object returned from FusionAuth /api/login.
22 | */
23 | const setUser = user => (dispatch, getState) => {
24 | // Grab only the pertinent information to send to storage. We don't want everything.
25 | const savedUser = {
26 | email: user.email,
27 | username: user.username,
28 | firstName: user.firstName,
29 | lastName: user.lastName
30 | }
31 |
32 | // Make sure the user supplied to the function is not null / empty.
33 | if (!user) {
34 | // Get the language data.
35 | const languageData = getState("language").language.languageData;
36 |
37 | // Return with an error message for a toast event.
38 | return Toasty.notify({
39 | toastType: Toasty.error(),
40 | toastMessage: get(languageData, ["common", "auth", "noUser"])
41 | });
42 | }
43 |
44 | // Dispatch the result.
45 | dispatch({
46 | type: SET_USER,
47 | payload: {
48 | info: savedUser
49 | }
50 | });
51 | }
52 |
53 | /**
54 | * Logout User
55 | *
56 | * Logs out the user by using the LOGOUT_USER action type. This will
57 | * set the user.info object to null.
58 | */
59 | const logoutUser = () => (dispatch, getState) => {
60 | // Get the language data.
61 | const languageData = getState("language").language.languageData;
62 |
63 | // The user just logged out, so let them know.
64 | Toasty.notify({
65 | type: Toasty.success(),
66 | content: get(languageData, ["common", "auth", "loggedOut"])
67 | });
68 |
69 | // Dispatch the result.
70 | dispatch({
71 | type: LOGOUT_USER
72 | });
73 | }
74 |
75 | // Export the actions.
76 | export {
77 | setUser,
78 | logoutUser
79 | }
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/index.jsx:
--------------------------------------------------------------------------------
1 | // Import reducer.
2 | import rootReducer from "./rootReducer";
3 |
4 | // Export reducer.
5 | export default rootReducer;
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/languageReducer.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Language Reducer
3 | *
4 | * Creates a Redux reducer for handling language actions.
5 | */
6 |
7 | // Action Types
8 | import { SET_LANGUAGE } from "../actionTypes";
9 |
10 | // Language Data
11 | import common_en from "../../data/language/en/common.json";
12 |
13 | // Setup initial state with the English language.
14 | const initialState = {
15 | locale: "en",
16 | languageData: {
17 | common: common_en
18 | }
19 | };
20 |
21 | // Export the Language Reducer.
22 | export default (state = initialState, action) => {
23 | switch (action.type) {
24 | case SET_LANGUAGE: {
25 | const { locale, languageData } = action.payload;
26 |
27 | return {
28 | ...state,
29 | locale,
30 | languageData
31 | };
32 | }
33 | default:
34 | return state;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/rootReducer.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Root Reducer
3 | *
4 | * Creates a Redux root reducer for handling all reducer actions in the
5 | * application.
6 | */
7 |
8 | // Dependencies
9 | import { combineReducers } from "redux";
10 |
11 | // Reducers
12 | import language from "./languageReducer";
13 | import user from "./userReducer";
14 |
15 | // Export the Redux reducer.
16 | export default combineReducers({
17 | language,
18 | user
19 | });
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/userReducer.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * User Reducer
3 | *
4 | * Creates a Redux reducer for handling user actions.
5 | */
6 |
7 | // Action Types
8 | import { SET_USER, LOGOUT_USER } from "../actionTypes";
9 |
10 | // Setup initial state with an empty user info object.
11 | const initialState = {
12 | info: null
13 | };
14 |
15 | // Export the User Reducer.
16 | export default (state = initialState, action) => {
17 | switch (action.type) {
18 | case SET_USER: {
19 | const { info } = action.payload;
20 |
21 | return {
22 | info
23 | };
24 | }
25 | case LOGOUT_USER: {
26 | return {
27 | info: null
28 | };
29 | }
30 | default:
31 | return state;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/src/redux/store/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import { store, persistor } from "./store";
3 |
4 | // Export modules
5 | export {
6 | store,
7 | persistor
8 | };
--------------------------------------------------------------------------------
/frontend/src/redux/store/store.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Redux Store
3 | *
4 | * Creates a Redux store for the application and passes the root reducer
5 | * to the store. Also applies the thunk middleware so that actions can
6 | * be dispatched asynchronously.
7 | */
8 |
9 | // Dependencies
10 | import { createStore, applyMiddleware } from "redux";
11 | import thunk from "redux-thunk";
12 | import { persistStore, persistReducer } from "redux-persist";
13 | import storage from "redux-persist/lib/storage";
14 |
15 | // Reducers
16 | import rootReducer from "../reducers";
17 |
18 | // Redux Persistence Config.
19 | const persistConfig = {
20 | key: "FA-Demo",
21 | storage
22 | }
23 |
24 | // Reducer for persisted config.
25 | const persistedReducer = persistReducer(persistConfig, rootReducer);
26 |
27 | // Create the Redux store.
28 | const store = createStore(persistedReducer, applyMiddleware(thunk));
29 | const persistor = persistStore(store);
30 |
31 | // Export the Redux store.
32 | export {
33 | store,
34 | persistor
35 | };
--------------------------------------------------------------------------------
/frontend/src/routes.js:
--------------------------------------------------------------------------------
1 | // Views
2 | import Home from "./views/Home";
3 | import Login from "./views/Auth/Login";
4 | import Register from "./views/Auth/Register";
5 | import ForgotPassword from "./views/Auth/ForgotPassword";
6 | import ChangePassword from "./views/Auth/ChangePassword";
7 | import { VerifyEmail, Verify2Factor } from "./views/Auth/Verify";
8 | import { UserChangePassword, User2Factor } from "./views/User";
9 | import { UserProfileView, UserProfileEdit } from "./views/User/UserProfile";
10 | import { TodoList, TodoModify, TodoView } from "./views/Todo";
11 |
12 | /**
13 | * Routes Array
14 | *
15 | * The routes array contains information about all of the available routes
16 | * in the application and is used to determine what text to display as well
17 | * as whether or not the route requires authentication to view.
18 | */
19 | const routes = [
20 | {
21 | path: "/auth/login/",
22 | name: "Login",
23 | component: Login
24 | }, {
25 | path: "/auth/register/",
26 | name: "Register",
27 | component: Register
28 | }, {
29 | path: "/auth/forgotPassword/",
30 | name: "Forgot Password",
31 | component: ForgotPassword
32 | }, {
33 | path: "/auth/ChangePassword/",
34 | name: "Change Password",
35 | component: ChangePassword
36 | }, {
37 | path: "/auth/ChangePassword/:changePasswordId",
38 | name: "Change Password",
39 | component: ChangePassword
40 | }, {
41 | path: "/auth/verify/email/",
42 | name: "Verify Email",
43 | component: VerifyEmail
44 | }, {
45 | path: "/auth/verify/email/:verificationId",
46 | name: "Verify Email",
47 | component: VerifyEmail
48 | }, {
49 | path: "/auth/verify/2fa/",
50 | name: "2FA Login",
51 | component: Verify2Factor
52 | }, {
53 | path: "/",
54 | name: "Home",
55 | component: Home
56 | }, {
57 | private: true,
58 | path: "/user/profile/",
59 | name: "User Profile",
60 | component: UserProfileView
61 | }, {
62 | private: true,
63 | path: "/user/profile/edit/",
64 | name: "Edit User Profile",
65 | component: UserProfileEdit
66 | }, {
67 | private: true,
68 | path: "/user/changePassword/",
69 | name: "User Change Password",
70 | component: UserChangePassword
71 | }, {
72 | private: true,
73 | path: "/user/2fa/",
74 | name: "User 2FA",
75 | component: User2Factor
76 | }, {
77 | private: true,
78 | path: "/todo/",
79 | name: "Todo List",
80 | component: TodoList
81 | }, {
82 | private: true,
83 | path: "/todo/add/",
84 | name: "Add ToDo",
85 | component: TodoModify
86 | }, {
87 | private: true,
88 | path: "/todo/edit/:id",
89 | name: "Edit ToDo",
90 | component: TodoModify
91 | }, {
92 | private: true,
93 | path: "/todo/:id",
94 | name: "View ToDo",
95 | component: TodoView
96 | }
97 | ];
98 |
99 | // Export the routes array.
100 | export default routes;
101 |
--------------------------------------------------------------------------------
/frontend/src/util/APIFetch/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import APIFetch from "./APIFetch";
3 |
4 | // Export modules
5 | export default APIFetch;
--------------------------------------------------------------------------------
/frontend/src/util/Auth/Auth.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Auth Module
3 | *
4 | * Contains function related to authentication within the application. It handles
5 | * login, logout, and page access determination for the frontend. It also manages
6 | * the user state so that useful information about the user can be displayed without
7 | * the need to query the backend or FusionAuth.
8 | */
9 |
10 | // Dependencies
11 | import axios from "axios";
12 | import { get } from "lodash";
13 |
14 | // Config
15 | import { config, links } from "../../config";
16 |
17 | // History
18 | import History from "../History";
19 |
20 | // Auth API endpoints
21 | import AuthAPI from "./AuthAPI";
22 |
23 | // Declare the Auth Module.
24 | const Auth = {};
25 |
26 | /**
27 | * Determine page access
28 | *
29 | * This checks whether or not a user is able to view a certain page, and this is
30 | * called before allowing the user to see the content of the page. This is independent
31 | * of any API endpoint call to obtain information for the page itself. This is used
32 | * over local storage to remove the frontend's ability to determine access.
33 | *
34 | * @param {Function} logoutUser Redux action to unset the user.
35 | * @param {string} path The path of the URL the user is trying to access.
36 | * @param {String} locale The current locale of the application.
37 | * @param {Object} languageData Current language information for the app. Language data object.
38 | */
39 | Auth.canAccessPage = ({ logoutUser, path, locale, languageData }) => {
40 | // Make a request to the API server and return a promise with the result.
41 | return new Promise((resolve, reject) => {
42 | axios({
43 | baseURL: config.apiServer.BASEURL,
44 | url: AuthAPI.canAccessPage.PATH_SEARCH,
45 | method: AuthAPI.canAccessPage.PATH_METHOD,
46 | withCredentials: true,
47 | data: {
48 | path
49 | },
50 | headers: {
51 | locale
52 | }
53 | }).then(response =>
54 | // If the status is 200, then the user has access, otherwise, deny access.
55 | response.status === 200 ? resolve() : reject()
56 | ).catch(({ response }) => {
57 | // Check if the response object exists (it will not exist if the API service
58 | // cannot be reached).
59 | if (!response) {
60 | return reject(get(languageData, ["common", "apiDown"]));
61 | }
62 |
63 | // If the the API service says that we need to login again, do so.
64 | if (response.data.loginAgain) {
65 | logoutUser();
66 |
67 | // Redirect the user to the home page.
68 | History.push(links.home);
69 | }
70 |
71 | // Return the error message.
72 | return reject(response.data);
73 | });
74 | });
75 | };
76 |
77 | // Export the Auth Controller.
78 | export default Auth;
--------------------------------------------------------------------------------
/frontend/src/util/Auth/AuthAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the Auth API.
2 | export default {
3 | canAccessPage: {
4 | PATH_SEARCH: "/api/roles/canAccessPage",
5 | PATH_METHOD: "post"
6 | }
7 | };
--------------------------------------------------------------------------------
/frontend/src/util/Auth/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Auth from "./Auth";
3 |
4 | // Export modules
5 | export default Auth;
--------------------------------------------------------------------------------
/frontend/src/util/FormHandler/FormHandler.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Form Handler
3 | *
4 | * Contains the methods for handling API responses and providing the proper
5 | * redirect link or error type and message for either form based alerts, or
6 | * for the Toasty module.
7 | */
8 |
9 | // Dependencies
10 | import { get, isEmpty } from "lodash";
11 | import classNames from "classnames";
12 |
13 | // Declare the Response Handler
14 | const FormHandler = {};
15 |
16 | /**
17 | * Get the class names for an input
18 | *
19 | * Determines the proper class names for an input based on whether it is
20 | * in a success or error state.
21 | *
22 | * @param {Object} formData Collection of input data from the form being checked.
23 | * @param {String} input Name of the input
24 | */
25 | FormHandler.inputStatus = (formData, input) => {
26 | // Get the error state of the input.
27 | const error = get(formData, [input, "error"]);
28 |
29 | // Return the class name for the given state.
30 | if (error === true) {
31 | return classNames("is-invalid");
32 | } else if (error === false) {
33 | return classNames("is-valid");
34 | } else {
35 | // Defaults to neither success nor error state.
36 | return classNames();
37 | }
38 | };
39 |
40 | /**
41 | * Validate form inputs
42 | *
43 | * Perform input validation on form blur and update the `formData` as necessary.
44 | *
45 | * @param {String || Array} form String or array for path to the language data for the input.
46 | * @param {String} target Name of the input to set validation info for.
47 | * @param {Array} error Error list for the input.
48 | * @param {Object} languageData Current language information for the app. Language data object.
49 | */
50 | FormHandler.validate = (form, target, error, languageData) => {
51 | // Setup variables for setting state.
52 | let isError, errorText, validText;
53 |
54 | // Check if there is an error with the input. Set the state variables appropriately.
55 | if (!isEmpty(error)) {
56 | isError = true;
57 | errorText = error;
58 | validText = "";
59 | } else {
60 | isError = false;
61 | errorText = "";
62 | validText = get(languageData, ["common", ...form, target, "validText"]);
63 | }
64 |
65 | // Return the information.
66 | return { isError, errorText, validText };
67 | };
68 |
69 | // Export the Form Handler.
70 | export default FormHandler;
--------------------------------------------------------------------------------
/frontend/src/util/FormHandler/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import FormHandler from "./FormHandler";
3 |
4 | // Export modules
5 | export default FormHandler;
--------------------------------------------------------------------------------
/frontend/src/util/History/History.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * History Utility
3 | *
4 | * This creates a representation of the history of the application
5 | * that can be utilized throughout the different components without the need
6 | * to pass the history property from the route (App -> Dashboard -> new component)
7 | * to each component.
8 | */
9 |
10 | // Dependencies
11 | import { createBrowserHistory } from "history";
12 |
13 | // Export the history module.
14 | export default createBrowserHistory();
--------------------------------------------------------------------------------
/frontend/src/util/History/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import History from "./History";
3 |
4 | // Export modules
5 | export default History;
--------------------------------------------------------------------------------
/frontend/src/util/Language/Language.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Language Module
3 | *
4 | * Contains function related to language methods for the application. Handles
5 | * getting the language data for the preferred language.
6 | */
7 |
8 | // Languages
9 | import common_en from "../../data/language/en/common.json";
10 | import common_es from "../../data/language/es/common.json";
11 |
12 | // Declare the Language Module object.
13 | const Language = {};
14 |
15 | /**
16 | * Get application language data
17 | *
18 | * Gets the application's language data based on the preferred language of the user.
19 | * This will be English by default.
20 | *
21 | * @param {String} preferredLanguage Preferred language for application
22 | */
23 | Language.getLanguageData = preferredLanguage => {
24 | // Set an object that we'll fill in with the switch statement.
25 | let langData;
26 |
27 | // Check the preferred language against available languages. Default to English.
28 | switch (preferredLanguage) {
29 | case "es":
30 | langData = {
31 | common: common_es
32 | };
33 | break;
34 | default:
35 | langData = {
36 | common: common_en
37 | };
38 | }
39 |
40 | // Return the language data for the application.
41 | return langData;
42 | }
43 |
44 | // Export the Language Module.
45 | export default Language;
--------------------------------------------------------------------------------
/frontend/src/util/Language/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Language from "./Language";
3 |
4 | // Export modules
5 | export default Language;
--------------------------------------------------------------------------------
/frontend/src/util/Logout/Logout.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Logout Module
3 | *
4 | * Contains functions related to logging out of the application.
5 | */
6 |
7 | // Dependencies
8 | import axios from "axios";
9 | import { get } from "lodash";
10 |
11 | // Config
12 | import { config } from "../../config";
13 |
14 | // Logout API endpoints
15 | import LogoutAPI from "./LogoutAPI";
16 |
17 | // Declare the Logout Module.
18 | const LogoutController = {};
19 |
20 | /**
21 | * Logout the current user
22 | *
23 | * Performs a logout by requesting the API service to remove the access token
24 | * and refresh token cookies that were set to keep the user logged in.
25 | *
26 | * @param {String} locale The current locale of the application.
27 | * @param {Object} languageData Current language information for the app. Language data object.
28 | */
29 | LogoutController.logout = (locale, languageData) => {
30 | return new Promise((resolve, reject) => {
31 | // Make the request to the API service.
32 | axios({
33 | baseURL: config.apiServer.BASEURL,
34 | url: LogoutAPI.apiService.getLogout.PATH_SEARCH,
35 | method: LogoutAPI.apiService.getLogout.PATH_METHOD,
36 | withCredentials: true,
37 | headers: {
38 | locale
39 | }
40 | }).then(() => resolve())
41 | .catch(() => reject(get(languageData, ["common", "auth", "logoutError"])));
42 | });
43 | };
44 |
45 | // Export the Logout Module.
46 | export default LogoutController;
--------------------------------------------------------------------------------
/frontend/src/util/Logout/LogoutAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the Logout API.
2 | export default {
3 | apiService: {
4 | getLogout: {
5 | PATH_SEARCH: "/api/fusionAuth/logout",
6 | PATH_METHOD: "delete"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/util/Logout/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Logout from "./Logout";
3 |
4 | // Export modules
5 | export default Logout;
--------------------------------------------------------------------------------
/frontend/src/util/PrivateRoute/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Private Route Component
3 | *
4 | * This Private Route utilizes a connection to an API server to determine whether or not
5 | * a user is able to view a certain page. This works on a validated JWT as well as user
6 | * and application roles. Some may utilize local storage for this ability, however, this
7 | * route chooses a more stringent method to ensure users only access content they
8 | * should be able to access. This will add some load time to each page request due
9 | * to the network request. It's a trade off that you must consider.
10 | */
11 |
12 | // Dependencies
13 | import React, { useState, useEffect } from "react";
14 | import { Route } from "react-router-dom";
15 | import { connect } from "react-redux";
16 |
17 | // Components
18 | import Authenticating from "../../components/Util/Authenticating";
19 | import NotAuthorized from "../../components/Util/NotAuthorized";
20 |
21 | // Redux Actions
22 | import { logoutUser } from "../../redux/actions";
23 |
24 | // Toast
25 | import Toasty from "../../util/Toasty";
26 |
27 | // Authentication Methods
28 | import Auth from "../Auth";
29 |
30 | /**
31 | * Private Route
32 | *
33 | * Checks whether or not the user is authenticated in order to access the private route.
34 | * If not, the user is redirected to the login page.
35 | *
36 | * @param {Object} component React component
37 | * @param {Function} logoutUser Redux action to unset the user.
38 | * @param {String} locale The current locale of the application.
39 | * @param {Object} languageData Current language information for the app. Language data object.
40 | */
41 | const PrivateRoute = ({ component: Component, logoutUser, locale, languageData, ...rest }) => {
42 | // Setup initial state.
43 | const [canAccessPage, setCanAccessPage] = useState(null);
44 |
45 | // React hook
46 | useEffect(() => {
47 | // Set a variable so we can cancel the request if needed (ex, user
48 | // moves to a new page).
49 | let didCancel = false;
50 |
51 | /**
52 | * Determine if a user can access a page.
53 | *
54 | * Makes a call to the Auth Controller to see if a user can access the page
55 | * in question. It also makes sure that the user has not moved to a new page
56 | * before trying to update the state.
57 | */
58 | const determineAccess = () => {
59 | // Make sure we don't try to change state after re-render.
60 | if (!didCancel) {
61 | Auth.canAccessPage({ locale, logoutUser, ...rest })
62 | .then(() => !didCancel && setCanAccessPage(true))
63 | .catch(response => {
64 | // Let the user know they cannot access the page.
65 | Toasty.notify({
66 | type: Toasty.error(),
67 | content: response.message
68 | });
69 |
70 | // Make sure we don't try to change state after re-render.
71 | if (!didCancel) {
72 | // Set the access page variable so the application can redirect the user.
73 | setCanAccessPage(false);
74 | }
75 | });
76 | }
77 | };
78 |
79 | // Call the page access method.
80 | determineAccess();
81 |
82 | /**
83 | * Perform action when the component is unmounted
84 | *
85 | * The return function in useEffect is equivalent to componentWillUnmount and
86 | * can be used to cancel the API request.
87 | */
88 | return () => {
89 | // Set the canceled variable to true.
90 | didCancel = true;
91 | };
92 |
93 | // eslint-disable-next-line
94 | }, [Component]);
95 |
96 | // Render the Private Route, or send the user to the homepage.
97 | return
100 | canAccessPage !== null
101 | ? canAccessPage
102 | ?
103 | :
104 | :
105 | }
106 | />
107 | };
108 |
109 | /**
110 | * Get App State
111 | *
112 | * Get the requried state for the component from the Redux store.
113 | *
114 | * @param {Object} state Application state from Redux.
115 | */
116 | const mapStateToProps = state => {
117 | return {
118 | locale: state.language.locale,
119 | languageData: state.language.languageData
120 | }
121 | }
122 |
123 | // Export the Private Route Component.
124 | export default connect(mapStateToProps, { logoutUser })(PrivateRoute);
--------------------------------------------------------------------------------
/frontend/src/util/PrivateRoute/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import PrivateRoute from "./PrivateRoute";
3 |
4 | // Export modules
5 | export default PrivateRoute;
--------------------------------------------------------------------------------
/frontend/src/util/ResponseHandler/ResponseHandler.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Response Handler
3 | *
4 | * Contains the methods for handling API responses and providing the proper
5 | * redirect link or error type and message for either form based alerts, or
6 | * for the Toasty module. Also handles actions for API responses.
7 | */
8 |
9 | // Dependencies
10 | import { get } from "lodash";
11 |
12 | // History
13 | import History from "../History";
14 | import Toasty from "../Toasty";
15 |
16 | // Declare the Response Handler
17 | const ResponseHandler = {};
18 |
19 | /**
20 | * Response Status
21 | *
22 | * Handle response statuses so that the views know how to handle them. Returns
23 | * a promise that resolves with a redirect link, or rejects with an error object.
24 | *
25 | * @param {Number} response The response from the API request.
26 | * @param {Boolean} apiMessage A boolean on whether or not to use the API response message.
27 | * @param {Object} languageData Current language information for the app. Current language information for the app.
28 | * @param {Array} langKey Key for which status code message to display
29 | * @param {Object} alertData Status codes and their respective alert type.
30 | * @param {Object} redirectData Status codes and their respective redirect link.
31 | */
32 | ResponseHandler.status = ({ response, apiMessage, languageData, langKey, alertData, redirectData = {} }) => {
33 | // Get the status from the response.
34 | const status = response.status;
35 |
36 | // Return a promise.
37 | return new Promise((resolve, reject) => {
38 | // Check if the status is in the redirect object.
39 | if (redirectData.hasOwnProperty(status)) {
40 | // Check if the redirect requires a Toast notification.
41 | if (redirectData[status].hasOwnProperty("alert")) {
42 | // Get the proper message for the Toast notification.
43 | const message = apiMessage
44 | ? response.data.message
45 | : get(languageData, [...langKey, status])
46 | ? get(languageData, [...langKey, status])
47 | : get(languageData, [...langKey, 500]);
48 |
49 | Toasty.notify({
50 | type: Toasty.info(),
51 | content: message
52 | })
53 | }
54 |
55 | // Resolve with the link to redirect to.
56 | resolve({
57 | redirect: redirectData[status]["redirect"]
58 | });
59 | } else {
60 | // Get the alert type if it exists, or default to status 500 alert type.
61 | const alertType = alertData.hasOwnProperty(status) ? alertData[status] : alertData[500];
62 |
63 | // Check if we should return the response from the API or find the response in the
64 | // language files.
65 | if (apiMessage) {
66 | reject({
67 | type: alertType,
68 | content: response.data.message
69 | });
70 | } else {
71 | // Check if the language key exists for the status to reject an error object
72 | // with the message for the status or 500, and the alert type.
73 | if (get(languageData, [...langKey, status])) {
74 | reject({
75 | type: alertType,
76 | content: get(languageData, [...langKey, status])
77 | });
78 | } else {
79 | reject({
80 | type: alertType,
81 | content: get(languageData, [...langKey, 500])
82 | });
83 | }
84 | }
85 | }
86 | });
87 | };
88 |
89 | /**
90 | * Handle app actions
91 | *
92 | * A custom action handler that handles redirects and errors for the user.
93 | *
94 | * @param {Object} responseObject The object response with alert info or redirect info.
95 | * @param {Object} data Data object required for handling the action.
96 | * @param {Function} action Redux action to handle data.
97 | */
98 | ResponseHandler.action = (responseObject, data, action) => {
99 | if (responseObject.redirect) {
100 | // If the redirect is to the homepage, then the user has been logged in, so
101 | // we must log the user in on the frontend.
102 | if (responseObject.setUser && data.user) {
103 | // Use the redux action to handle the user object.
104 | action(data.user);
105 | }
106 |
107 | // Redirect the user to the location in the response object.
108 | History.push({
109 | pathname: responseObject.redirect,
110 | state: {
111 | twoFactorId: get(data, "twoFactorId"),
112 | changePasswordId: get(data, "changePasswordId"),
113 | from: get(responseObject, "from"),
114 | displayMessage: true
115 | }
116 | });
117 | } else {
118 | // Return with the responseObject since there was an error.
119 | return responseObject;
120 | }
121 | };
122 |
123 | // Export the Response Handler.
124 | export default ResponseHandler;
--------------------------------------------------------------------------------
/frontend/src/util/ResponseHandler/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import ResponseHandler from "./ResponseHandler";
3 |
4 | // Export modules
5 | export default ResponseHandler;
--------------------------------------------------------------------------------
/frontend/src/util/Toasty/Toasty.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Toast Utility
3 | *
4 | * Contains the information about the "react-toastify" package and maintains
5 | * the ability to create toasts utilizing that package. These toasts are helpful
6 | * for displaying temporary information to users.
7 | */
8 |
9 | // Dependencies
10 | import { toast } from "react-toastify";
11 |
12 | // Declare the Toasty Utility.
13 | const Toasty = {};
14 |
15 | /**
16 | * Toast Notify
17 | *
18 | * Create a toast notification with the information supplied, or the defaults
19 | * specified below. This is used for creating useful temporary notifications where
20 | * you may not want to "permanently" display something on the screen, like the "Alert"
21 | * from bootstrap / reactstrap.
22 | */
23 | Toasty.notify = ({ type, content, position, autoClose }) => {
24 | // Create a toast.
25 | toast(content, {
26 | position: position || "top-right",
27 | type: type || Toasty.info(),
28 | autoClose: autoClose || 3000
29 | });
30 | };
31 |
32 | /**
33 | * Toast Default
34 | *
35 | * Returns the Toast default type.
36 | */
37 | Toasty.default = () => {
38 | return toast.TYPE.DEFAULT;
39 | };
40 |
41 | /**
42 | * Toast Info
43 | *
44 | * Returns the Toast info type.
45 | */
46 | Toasty.info = () => {
47 | return toast.TYPE.INFO;
48 | };
49 |
50 | /**
51 | * Toast Success
52 | *
53 | * Returns the Toast success type.
54 | */
55 | Toasty.success = () => {
56 | return toast.TYPE.SUCCESS;
57 | };
58 |
59 | /**
60 | * Toast Warning
61 | *
62 | * Returns the Toast warning type.
63 | */
64 | Toasty.warning = () => {
65 | return toast.TYPE.WARNING;
66 | };
67 |
68 | /**
69 | * Toast Error
70 | *
71 | * Returns the Toast error type.
72 | */
73 | Toasty.error = () => {
74 | return toast.TYPE.ERROR;
75 | };
76 |
77 | // Export the Toasty Utility.
78 | export default Toasty;
--------------------------------------------------------------------------------
/frontend/src/util/Toasty/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Toasty from "./Toasty";
3 |
4 | // Export modules
5 | export default Toasty;
--------------------------------------------------------------------------------
/frontend/src/util/ValidateInput/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import ValidateInput from "./ValidateInput";
3 |
4 | // Export modules
5 | export default ValidateInput;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/ChangePassword/ChangePasswordAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the Change Password API.
2 | export default {
3 | apiService: {
4 | postChangePassword: {
5 | PATH_SEARCH: "/api/fusionAuth/changePassword",
6 | PATH_METHOD: "post"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/Auth/ChangePassword/ChangePasswordController.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Change Password Controller
3 | *
4 | * Contains functions related to handling Change Password responses and actions.
5 | * Enables redirecting the user depending on the response from the API
6 | * service. Also provides alert data for the view if needed.
7 | */
8 |
9 | // Dependencies
10 | import { get } from "lodash";
11 |
12 | // Config & Application Links
13 | import { links } from "../../../config";
14 |
15 | // Response Handler
16 | import ResponseHandler from "../../../util/ResponseHandler";
17 |
18 | // Declare the Change Password Controller.
19 | const ChangePasswordController = {};
20 |
21 | /**
22 | * Determine post Change Password response action
23 | *
24 | * Function takes in the status of the Change Password response in order to
25 | * determine the post Change Password action to be taken, whether that is to
26 | * display an error message, or to redirect the user. A promise is used
27 | * to redirect the user while reject is used to display a message.
28 | *
29 | * @param {Number} response The response from the API request.
30 | * @param {Object} languageData Current language information for the app. Language data object.
31 | */
32 | ChangePasswordController.handleResponse = async (response, languageData) => {
33 | // Redirect data for the redirectable statuses.
34 | const redirectData = {
35 | "200": {
36 | redirect: links.home,
37 | alert: true
38 | }
39 | };
40 | // Alert data information for statuses. All statuses in this case use
41 | // the `danger` type, so we default all statuses to this with `500` as the key.
42 | const alertData = {
43 | "500": "danger"
44 | }
45 |
46 | // Return a promise with the results.
47 | return new Promise(async (resolve, reject) => {
48 | try {
49 | // Resolve with the redirect link.
50 | resolve(await ResponseHandler.status({ response, apiMessage: true, languageData, langKey: null, redirectData, alertData }));
51 | } catch (error) {
52 | // Reject with the error object.
53 | reject(error);
54 | }
55 | });
56 | };
57 |
58 | /**
59 | * Handle Change Password Actions
60 | *
61 | * Uses the custom Response Handler to handle Change Password actions.
62 | *
63 | * @param {Object} responseObject Response object from the request with alert info or redirect info.
64 | * @param {Object} data Data object required for handling the action.
65 | * @param {Function} action Redux action to handle data.
66 | */
67 | ChangePasswordController.handleAction = (responseObject, data, action) => {
68 | // Add additional content to the response object to be handled.
69 | responseObject = {
70 | ...responseObject,
71 | setUser: true
72 | }
73 |
74 | // Use the custom Response Handler to determine what to do with the user.
75 | const response = ResponseHandler.action(responseObject, data, action);
76 |
77 | // Check if there are any errors to display.
78 | if (get(response, "type")) {
79 | // Return the error information.
80 | return response;
81 | } else {
82 | // No error, so the user was redirected.
83 | return false;
84 | }
85 | }
86 |
87 | // Export the Change Password Controller.
88 | export default ChangePasswordController;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/ChangePassword/ChangePasswordForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColXL": 8,
4 | "inputColMD": 12,
5 | "inputColClassName": "mx-auto",
6 | "type": "password",
7 | "name": "password",
8 | "prependIcon": "lock",
9 | "validation": {
10 | "required": {
11 | "langKey": [
12 | "common",
13 | "input",
14 | "password",
15 | "validation",
16 | "required"
17 | ]
18 | },
19 | "minLength": {
20 | "rule": 8,
21 | "langKey": [
22 | "common",
23 | "input",
24 | "password",
25 | "validation",
26 | "minLength"
27 | ]
28 | },
29 | "max": {
30 | "rule": 256,
31 | "langKey": [
32 | "common",
33 | "input",
34 | "password",
35 | "validation",
36 | "max"
37 | ]
38 | }
39 | }
40 | },
41 | {
42 | "inputColXL": 8,
43 | "inputColMD": 12,
44 | "inputColClassName": "mx-auto",
45 | "type": "password",
46 | "name": "confirmPassword",
47 | "prependIcon": "lock",
48 | "validation": {
49 | "required": {
50 | "langKey": [
51 | "common",
52 | "input",
53 | "confirmPassword",
54 | "validation",
55 | "required"
56 | ]
57 | },
58 | "confirmValue": {
59 | "langKey": [
60 | "common",
61 | "input",
62 | "confirmPassword",
63 | "validation",
64 | "confirmValue"
65 | ]
66 | }
67 | }
68 | }
69 | ]
--------------------------------------------------------------------------------
/frontend/src/views/Auth/ChangePassword/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import ChangePassword from "./ChangePassword";
3 |
4 | // Export modules
5 | export default ChangePassword;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/ForgotPassword/ForgotPasswordAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the Forgot Password API.
2 | export default {
3 | apiService: {
4 | postForgotPassword: {
5 | PATH_SEARCH: "/api/fusionAuth/forgotPassword",
6 | PATH_METHOD: "post"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/Auth/ForgotPassword/ForgotPasswordController.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Forgot Password Controller
3 | *
4 | * Contains functions related to handling Forgot Password responses and actions.
5 | * Enables redirecting the user depending on the response from the FusionAuth
6 | * server. Also provides alert data for the view if needed.
7 | */
8 |
9 | // Dependencies
10 | import { get } from "lodash";
11 |
12 | // Config & Application Links
13 | import { links } from "../../../config";
14 |
15 | // Response Handler
16 | import ResponseHandler from "../../../util/ResponseHandler";
17 |
18 | // Declare the Forgot Password Controller.
19 | const ForgotPasswordController = {};
20 |
21 | /**
22 | * Determine post Forgot Password response action
23 | *
24 | * Function takes in the status of the Forgot Password response in order to
25 | * determine the post Forgot Password action to be taken, whether that is to
26 | * display an error message, or to redirect the user. A promise is used
27 | * to redirect the user while reject is used to display a message.
28 | *
29 | * @param {number} response The response from the API request.
30 | * @param {Object} languageData Current language information for the app. Language data object.
31 | */
32 | ForgotPasswordController.handleResponse = async (response, languageData) => {;
33 | // Redirect data for the redirectable statuses.
34 | const redirectData = {
35 | "200": {
36 | redirect: links.auth.changePassword,
37 | alert: true
38 | }
39 | };
40 | // Alert data information for statuses. All statuses in this case use
41 | // the `danger` type, so we default all statuses to this with `500` as the key.
42 | const alertData = {
43 | "500": "danger"
44 | }
45 |
46 | // Return a promise with the results.
47 | return new Promise(async (resolve, reject) => {
48 | try {
49 | // Resolve with the redirect link.
50 | resolve(await ResponseHandler.status({ response, apiMessage: true, languageData, langKey: null, redirectData, alertData }));
51 | } catch (error) {
52 | // Reject with the error object.
53 | reject(error);
54 | }
55 | });
56 | };
57 |
58 | /**
59 | * Handle Forgot Password Actions
60 | *
61 | * Uses the custom Response Handler to handle Forgot Password actions.
62 | *
63 | * @param {Object} responseObject Response object from the request with alert info or redirect info.
64 | * @param {Object} data Data object required for handling the action.
65 | * @param {Function} action Redux action to handle data.
66 | */
67 | ForgotPasswordController.handleAction = (responseObject, data, action) => {
68 | // Add additional content to the response object to be handled.
69 | responseObject = {
70 | ...responseObject,
71 | from: "changeForogtPassword"
72 | }
73 |
74 | // Use the custom Response Handler to determine what to do with the user.
75 | const response = ResponseHandler.action(responseObject, data, action);
76 |
77 | // Check if there are any errors to display.
78 | if (get(response, "type")) {
79 | // Return the error information.
80 | return response;
81 | } else {
82 | // No error, so the user was redirected.
83 | return false;
84 | }
85 | }
86 |
87 | // Export the Forgot Password Controller.
88 | export default ForgotPasswordController;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/ForgotPassword/ForgotPasswordForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColXL": 8,
4 | "inputColMD": 12,
5 | "inputColClassName": "mx-auto",
6 | "type": "test",
7 | "name": "loginId",
8 | "prependIcon": "user",
9 | "validation": {
10 | "required": {
11 | "langKey": [
12 | "common",
13 | "input",
14 | "loginId",
15 | "validation",
16 | "required"
17 | ]
18 | }
19 | }
20 | }
21 | ]
--------------------------------------------------------------------------------
/frontend/src/views/Auth/ForgotPassword/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import ForgotPassword from "./ForgotPassword";
3 |
4 | // Export modules
5 | export default ForgotPassword;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Login/LoginAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the Login API.
2 | export default {
3 | fusionAuth: {
4 | getLogin: {
5 | PATH_SEARCH: "/api/login",
6 | PATH_METHOD: "post"
7 | }
8 | },
9 | apiService: {
10 | getLogin: {
11 | PATH_SEARCH: "/api/fusionAuth/login",
12 | PATH_METHOD: "post"
13 | }
14 | }
15 | };
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Login/LoginController.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Login Controller
3 | *
4 | * Contains functions related to login within the application. It handles
5 | * login response from the FusionAuth server and also sets the cookies with
6 | * the API server for subsequent requests that required authentication.
7 | */
8 |
9 | // Dependencies
10 | import axios from "axios";
11 | import { get } from "lodash";
12 |
13 | // Config & Application Links
14 | import { config, links } from "../../../config";
15 |
16 | // Login API endpoints
17 | import LoginAPI from "./LoginAPI";
18 |
19 | // Response Handler
20 | import ResponseHandler from "../../../util/ResponseHandler";
21 |
22 | // Declare the Login Controller.
23 | const LoginController = {};
24 |
25 | /**
26 | * Determine post login action (from FusionAuth)
27 | *
28 | * Function takes in the status of the login response in order to
29 | * determine the post login action to be taken, whether that is to
30 | * display an error message, or to redirect the user. A promise is used
31 | * to redirect the user while reject is used to display a message.
32 | *
33 | * @param {number} response The response from the API request.
34 | * @param {Object} languageData Current language information for the app. Language data object.
35 | */
36 | LoginController.handleResponse = async (response, languageData) => {
37 | // Base path for language keys for the statuses.
38 | const langKey = ["common", "auth", "login", "status"];
39 | // Redirect data for the redirectable statuses.
40 | const redirectData = {
41 | "200": {
42 | redirect: links.home
43 | },
44 | "203":{
45 | redirect: links.auth.changePassword,
46 | alert: true
47 | },
48 | "242": {
49 | redirect: links.auth.twoFactor,
50 | alert: true
51 | }
52 | };
53 | // Alert data information for statuses. All statuses in this case use
54 | // the `danger` type, so we default all statuses to this with `500` as the key.
55 | const alertData = {
56 | "500": "danger"
57 | }
58 |
59 | // Return a promise with the results.
60 | return new Promise(async (resolve, reject) => {
61 | try {
62 | // Resolve with the redirect link.
63 | resolve(await ResponseHandler.status({ response, apiMessage: false, languageData, langKey, redirectData, alertData }));
64 | } catch (error) {
65 | // Reject with the error object.
66 | reject(error);
67 | }
68 | });
69 | };
70 |
71 | /**
72 | * Handle Login Actions
73 | *
74 | * Uses the custom Response Handler to handle login actions.
75 | *
76 | * @param {Object} responseObject Response object from the request with alert info or redirect info.
77 | * @param {Object} data Data object required for handling the action.
78 | * @param {Function} action Redux action to handle data.
79 | */
80 | LoginController.handleAction = (responseObject, data, action) => {
81 | // Add additional content to the response object to be handled.
82 | responseObject = {
83 | ...responseObject,
84 | setUser: true,
85 | from: "changePassword"
86 | }
87 |
88 | // Use the custom Response Handler to determine what to do with the user.
89 | const response = ResponseHandler.action(responseObject, data, action);
90 |
91 | // Check if there are any errors to display.
92 | if (get(response, "type")) {
93 | // Return the error information.
94 | return response;
95 | } else {
96 | // No error, so the user was redirected.
97 | return false;
98 | }
99 | }
100 |
101 | /**
102 | * Set cookies on successful login
103 | *
104 | * Function makes a request to the backend API service to set cookies
105 | * for future requests in order to allow the user to access the API endpoints
106 | * for the application.
107 | *
108 | * @param {string} refreshToken User refresh token from the login response.
109 | * @param {string} token User access token from the login response.
110 | * @param {String} locale The current locale of the application.
111 | * @param {Object} languageData Current language information for the app. Language data object.
112 | */
113 | LoginController.setCookies = (refreshToken, token, locale, languageData) => {
114 | // Return a promise with the redirect url or the error response.
115 | return new Promise((resolve, reject) => {
116 | axios({
117 | baseURL: config.apiServer.BASEURL,
118 | url: LoginAPI.apiService.getLogin.PATH_SEARCH,
119 | method: LoginAPI.apiService.getLogin.PATH_METHOD,
120 | withCredentials: true,
121 | data: {
122 | refreshToken,
123 | token
124 | },
125 | headers: {
126 | locale
127 | }
128 | }).then(() => {
129 | resolve({ redirect: links.home })
130 | }).catch(() => reject(get(languageData, ["common", "auth", "failedLogin"])));
131 | });
132 | };
133 |
134 | // Export the Login Controller.
135 | export default LoginController;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Login/LoginForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColXL": 8,
4 | "inputColMD": 12,
5 | "inputColClassName": "mx-auto",
6 | "type": "text",
7 | "name": "loginId",
8 | "prependIcon": "user"
9 | },
10 | {
11 | "inputColXL": 8,
12 | "inputColMD": 12,
13 | "inputColClassName": "mx-auto",
14 | "type": "password",
15 | "name": "password",
16 | "prependIcon": "lock"
17 | }
18 | ]
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Login/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Login from "./Login";
3 |
4 | // Export modules
5 | export default Login;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Register/RegisterAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the Registration API.
2 | export default {
3 | apiService: {
4 | postRegister: {
5 | PATH_SEARCH: "/api/fusionAuth/registration/",
6 | PATH_METHOD: "post"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Register/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Register from "./Register";
3 |
4 | // Export modules
5 | export default Register;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Verify/Verify2Factor/Verify2FactorAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the Verify 2Factor API.
2 | export default {
3 | apiService: {
4 | post2FAVerfiy: {
5 | PATH_SEARCH: "/api/fusionAuth/verify/2fa",
6 | PATH_METHOD: "post"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Verify/Verify2Factor/Verify2FactorController.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Verify 2Factor Controller
3 | *
4 | * Contains functions related to handling 2Factor responses and actions.
5 | * Enables redirecting the user depending on the response from the API
6 | * services. Also provides alert data for the view if needed.
7 | */
8 |
9 | // Dependencies
10 | import { get } from "lodash";
11 |
12 | // Config & Application Links
13 | import { links } from "../../../../config";
14 |
15 | // Response Handler
16 | import ResponseHandler from "../../../../util/ResponseHandler";
17 |
18 | // Declare the Verify 2Factor Controller.
19 | const Verify2FactorController = {};
20 |
21 | /**
22 | * Determine post 2Factor login response action
23 | *
24 | * Function takes in the status of the 2Factor response in order to
25 | * determine the post 2FA action to be taken, whether that is to
26 | * display an error message, or to redirect the user. A promise is used
27 | * to redirect the user while reject is used to display a message.
28 | *
29 | * @param {number} response The response from the API request.
30 | * @param {Object} languageData Current language information for the app. Language data object.
31 | */
32 | Verify2FactorController.handleResponse = async (response, languageData) => {
33 | // Redirect data for the redirectable statuses.
34 | const redirectData = {
35 | "200": {
36 | redirect: links.home
37 | },
38 | "203": {
39 | redirect: links.auth.changePassword,
40 | alert: true
41 | }
42 | };
43 | // Alert data information for statuses. All statuses in this case use
44 | // the `danger` type, so we default all statuses to this with `500` as the key.
45 | const alertData = {
46 | "500": "danger"
47 | }
48 |
49 | // Return a promise with the results.
50 | return new Promise(async (resolve, reject) => {
51 | try {
52 | // Resolve with the redirect link.
53 | resolve(await ResponseHandler.status({ response, apiMessage: true, languageData, langKey: null, redirectData, alertData }));
54 | } catch (error) {
55 | // Reject with the error object.
56 | reject(error);
57 | }
58 | });
59 | };
60 |
61 | /**
62 | * Handle 2Factor Login Actions
63 | *
64 | * Uses the custom Response Handler to handle 2Factor actions.
65 | *
66 | * @param {Object} responseObject Response object from the request with alert info or redirect info.
67 | * @param {Object} data Data object required for handling the action.
68 | * @param {Function} action Redux action to handle data.
69 | */
70 | Verify2FactorController.handleAction = (responseObject, data, action) => {
71 | // Add additional content to the response object to be handled.
72 | responseObject = {
73 | ...responseObject,
74 | setUser: true,
75 | from: "changePassword"
76 | }
77 |
78 | // Use the custom Response Handler to determine what to do with the user.
79 | const response = ResponseHandler.action(responseObject, data, action);
80 |
81 | // Check if there are any errors to display.
82 | if (get(response, "type")) {
83 | // Return the error information.
84 | return response;
85 | } else {
86 | // No error, so the user was redirected.
87 | return false;
88 | }
89 | }
90 |
91 | // Export the Verify 2Factor Controller.
92 | export default Verify2FactorController;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Verify/Verify2Factor/Verify2FactorForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColXL": 8,
4 | "inputColMD": 12,
5 | "inputColClassName": "mx-auto",
6 | "type": "text",
7 | "name": "code",
8 | "prependIcon": "shield-alt",
9 | "validation": {
10 | "required": {
11 | "langKey": [
12 | "common",
13 | "input",
14 | "code",
15 | "validation",
16 | "required"
17 | ]
18 | },
19 | "length": {
20 | "rule": 6,
21 | "langKey": [
22 | "common",
23 | "input",
24 | "code",
25 | "validation",
26 | "length"
27 | ]
28 | }
29 | }
30 | }
31 | ]
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Verify/Verify2Factor/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Verify2Factor from "./Verify2Factor";
3 |
4 | // Export modules
5 | export default Verify2Factor;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Verify/VerifyEmail/VerifyEmailAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the Verify Email API.
2 | export default {
3 | apiService: {
4 | postEmailVerify: {
5 | PATH_SEARCH: "/api/fusionAuth/verify/email/",
6 | PATH_METHOD: "post"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Verify/VerifyEmail/VerifyEmailForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColXL": 8,
4 | "inputColMD": 12,
5 | "inputColClassName": "mx-auto",
6 | "type": "text",
7 | "name": "verificationId",
8 | "prependIcon": "code",
9 | "validation": {
10 | "required": {
11 | "langKey": [
12 | "common",
13 | "input",
14 | "verificationId",
15 | "validation",
16 | "required"
17 | ]
18 | },
19 | "length": {
20 | "rule": 43,
21 | "langKey": [
22 | "common",
23 | "input",
24 | "verificationId",
25 | "validation",
26 | "length"
27 | ]
28 | }
29 | }
30 | }
31 | ]
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Verify/VerifyEmail/VerifyEmailPageData.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 |
4 | // Components
5 | import PageData from "../../../../components/Util/PageData";
6 |
7 | /**
8 | * Verify Email Page Data Component
9 | *
10 | * Display the proper components for the user if the verification ID was
11 | * supplied to the page via the URL.
12 | *
13 | * @param {Boolean} isLoading Loading indication for API request.
14 | * @param {Object} hasError Error object from the API request.
15 | */
16 | const VerifyEmailPageData = ({ isLoading, hasError }) => {
17 | // Return the formatted result, with error and loading indication.
18 | return ;
22 | }
23 |
24 | // Export the Verify Email Page Data Component.
25 | export default VerifyEmailPageData;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Verify/VerifyEmail/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import VerifyEmail from "./VerifyEmail";
3 |
4 | // Export modules
5 | export default VerifyEmail;
--------------------------------------------------------------------------------
/frontend/src/views/Auth/Verify/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import VerifyEmail from "./VerifyEmail";
3 | import Verify2Factor from "./Verify2Factor";
4 |
5 | // Export modules
6 | export {
7 | VerifyEmail,
8 | Verify2Factor
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/Home.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { get } from "lodash";
4 | import { connect } from "react-redux";
5 | import {
6 | Card,
7 | CardHeader,
8 | CardBody,
9 | Container,
10 | Row,
11 | Col
12 | } from "reactstrap";
13 |
14 | /**
15 | * Home View
16 | *
17 | * Display some information on the home page about the NodeJS + React + FusionAuth
18 | * example application.
19 | *
20 | * @param {Object} languageData Current language information for the app. Language data object.
21 | */
22 | const Home = ({ languageData }) => (
23 |
24 |
25 |
26 |
27 |
28 | { get(languageData, ["common", "about"]) }
29 |
30 |
31 | { get(languageData, ["common", "aboutDesc"]) }
32 |
33 |
34 |
35 |
36 |
37 | );
38 |
39 | /**
40 | * Get App State
41 | *
42 | * Get the requried state for the component from the Redux store.
43 | *
44 | * @param {Object} state Application state from Redux.
45 | */
46 | const mapStateToProps = state => {
47 | return {
48 | languageData: state.language.languageData
49 | }
50 | }
51 |
52 | // Export the Home View.
53 | export default connect(mapStateToProps)(Home);
54 |
--------------------------------------------------------------------------------
/frontend/src/views/NotFound/NotFound.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { get } from "lodash";
4 | import { connect } from "react-redux";
5 | import {
6 | Card,
7 | CardHeader,
8 | CardBody,
9 | Container,
10 | Row,
11 | Col
12 | } from "reactstrap";
13 |
14 | /**
15 | * Not Found View
16 | *
17 | * For all not found requests, display a 404 Not Found message.
18 | *
19 | * @param {Object} languageData Current language information for the app. Language data object.
20 | */
21 | const NotFound = ({ languageData }) => (
22 |
23 |
24 |
25 |
26 |
27 | { get(languageData, ["common", "404", "title"]) }
28 |
29 |
30 | { get(languageData, ["common", "404", "message"]) }
31 |
32 |
33 |
34 |
35 |
36 | );
37 |
38 | /**
39 | * Get App State
40 | *
41 | * Get the requried state for the component from the Redux store.
42 | *
43 | * @param {Object} state Application state from Redux.
44 | */
45 | const mapStateToProps = state => {
46 | return {
47 | languageData: state.language.languageData
48 | }
49 | }
50 |
51 | // Export the Not Found View.
52 | export default connect(mapStateToProps)(NotFound);
--------------------------------------------------------------------------------
/frontend/src/views/NotFound/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import NotFound from "./NotFound";
3 |
4 | // Export modules
5 | export default NotFound;
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the ToDo API.
2 | export default {
3 | apiService: {
4 | getTodos: {
5 | PATH_SEARCH: "/api/todo",
6 | PATH_METHOD: "get"
7 | },
8 | addTodo: {
9 | PATH_SEARCH: "/api/todo",
10 | PATH_METHOD: "post"
11 | },
12 | editTodo: {
13 | PATH_SEARCH: "/api/todo",
14 | PATH_METHOD: "put"
15 | },
16 | getTodo: {
17 | PATH_SEARCH: "/api/todo/one",
18 | PATH_METHOD: "get"
19 | },
20 | deleteTodo: {
21 | PATH_SEARCH: "/api/todo",
22 | PATH_METHOD: "delete"
23 | }
24 | }
25 | };
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoList/TodoList.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { get } from "lodash";
4 | import { connect } from "react-redux";
5 | import { Link } from "react-router-dom";
6 | import classNames from "classnames";
7 | import {
8 | Card,
9 | CardHeader,
10 | CardBody,
11 | Container,
12 | Row,
13 | Col
14 | } from "reactstrap";
15 |
16 | // Config
17 | import { config, links } from "../../../config";
18 |
19 | // API
20 | import APIFetch from "../../../util/APIFetch";
21 | import TodoAPI from "../TodoAPI";
22 |
23 | // Components
24 | import CustomButton from "../../../components/CustomButton";
25 |
26 | // Page Data
27 | import TodoListPageData from "./TodoListPageData";
28 |
29 | /**
30 | * Todo List View
31 | *
32 | * The Todo view contains information for all of the user's ToDo items. From here,
33 | * they are able to view the individual Todo items.
34 | *
35 | * @param {String} locale The current locale of the application.
36 | * @param {Object} languageData Current language information for the app. Language data object.
37 | */
38 | const TodoList = ({ locale, languageData }) => {
39 | // Setup the API Fetch utility for the Todo List View.
40 | const [{ isLoading, results, hasError }] = APIFetch({
41 | locale,
42 | BASEURL: config.apiServer.BASEURL,
43 | PATH_SEARCH: TodoAPI.apiService.getTodos.PATH_SEARCH,
44 | PATH_METHOD: TodoAPI.apiService.getTodos.PATH_METHOD
45 | });
46 |
47 | // Create a dynamic class name for the panel that will center the text
48 | // if the result status is not 200.
49 | const isCenteredText = classNames({
50 | "mx-auto": isLoading || get(results, "status") !== 200
51 | });
52 |
53 | // Render the Todo List View.
54 | return (
55 |
56 |
57 |
58 |
59 |
60 | { get(languageData, ["common", "todo", "list", "title"]) }
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
76 |
77 |
78 |
79 |
80 | );
81 | }
82 |
83 | /**
84 | * Get App State
85 | *
86 | * Get the requried state for the component from the Redux store.
87 | *
88 | * @param {Object} state Application state from Redux.
89 | */
90 | const mapStateToProps = state => {
91 | return {
92 | locale: state.language.locale,
93 | languageData: state.language.languageData
94 | }
95 | }
96 |
97 | // Export the Todo List View.
98 | export default connect(mapStateToProps)(TodoList);
99 |
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoList/TodoListPageData.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { Link } from "react-router-dom";
4 | import { get, map } from "lodash";
5 | import { Col } from "reactstrap";
6 |
7 | // Components
8 | import PageData from "../../../components/Util/PageData";
9 |
10 | // Links
11 | import { links } from "../../../config";
12 |
13 | /**
14 | * ToDo List Page Data Component
15 | *
16 | * Display the user's ToDo list properly.
17 | *
18 | * @param {Boolean} isLoading Loading indication for API request.
19 | * @param {Object} results Result object from the API request.
20 | * @param {Object} hasError Error object from the API request.
21 | * @param {Object} languageData Current language information for the app. Language data object.
22 | */
23 | const TodoPageData = ({ isLoading, results, hasError, languageData }) => {
24 | // ToDo status indicators.
25 | const todoStatus = {
26 | true: { get(languageData, ["common", "done"]) },
27 | false: { get(languageData, ["common", "incomplete"]) }
28 | }
29 |
30 | // Display ToDo items for the current user if there are any. Otherwise, return nothing.
31 | // The default text is handled by the API service.
32 | const todoInfo = get(results, "data")
33 | ? map(results.data.content, (result, key) => (
34 |
35 | { todoStatus[result.done] }
36 |
37 |
38 | { result.name }
39 |
40 |
41 |
42 | { result.description }
43 |
44 | ))
45 | : null;
46 |
47 | // Return the formatted result, with error and loading indication.
48 | return ;
54 | }
55 |
56 | // Export the Todo List Page Data Component.
57 | export default TodoPageData;
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoList/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import TodoList from "./TodoList";
3 |
4 | // Export modules
5 | export default TodoList;
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoModify/TodoModifyForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColMD": 12,
4 | "inputColClassName": "mx-auto",
5 | "id": "done",
6 | "type": "checkbox",
7 | "name": "done",
8 | "formGroupClassName": "custom-control custom-control-alternative custom-checkbox",
9 | "labelClassName": "custom-control-label",
10 | "inputClassName": "custom-control-input"
11 | },
12 | {
13 | "inputColMD": 12,
14 | "inputColClassName": "mx-auto",
15 | "type": "text",
16 | "name": "name",
17 | "prependIcon": "tasks",
18 | "validation": {
19 | "required": {
20 | "langKey": [
21 | "common",
22 | "input",
23 | "name",
24 | "validation",
25 | "required"
26 | ]
27 | },
28 | "minLength": {
29 | "rule": 5,
30 | "langKey": [
31 | "common",
32 | "input",
33 | "name",
34 | "validation",
35 | "minLength"
36 | ]
37 | },
38 | "max": {
39 | "rule": 150,
40 | "langKey": [
41 | "common",
42 | "input",
43 | "name",
44 | "validation",
45 | "max"
46 | ]
47 | }
48 | }
49 | },
50 | {
51 | "inputColMD": 12,
52 | "inputColClassName": "mx-auto",
53 | "type": "textarea",
54 | "name": "description",
55 | "prependIcon": "feather-alt",
56 | "rows": 5,
57 | "validation": {
58 | "required": {
59 | "langKey": [
60 | "common",
61 | "input",
62 | "description",
63 | "validation",
64 | "required"
65 | ]
66 | },
67 | "minLength": {
68 | "rule": 10,
69 | "langKey": [
70 | "common",
71 | "input",
72 | "description",
73 | "validation",
74 | "minLength"
75 | ]
76 | }
77 | }
78 | }
79 | ]
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoModify/TodoModifyPageData.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 |
4 | // Components
5 | import PageData from "../../../components/Util/PageData";
6 |
7 | /**
8 | * ToDo Item Data Component
9 | *
10 | * Display the ToDo item of a given ID properly.
11 | *
12 | * @param {Boolean} isLoading Loading indication for API request.
13 | * @param {Object} results Result object from the API request.
14 | * @param {Object} hasError Error object from the API request.
15 | * @param {Function} component The component that needs to be diplayed, as a function.
16 | */
17 | const TodoPageData = ({ isLoading, results, hasError, component }) => {
18 | // Display the ToDo item for the current user with the given ID if it exists.
19 | const todoInfo = component();
20 |
21 | // Return the formatted result, with error and loading indication.
22 | return ;
28 | }
29 |
30 | // Export the ToDo Item Data Component.
31 | export default TodoPageData;
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoModify/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import TodoModify from "./TodoModify";
3 |
4 | // Export modules
5 | export default TodoModify;
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoView/TodoView.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React, { useEffect } from "react";
3 | import { get } from "lodash";
4 | import { connect } from "react-redux";
5 | import { Link } from "react-router-dom";
6 | import classNames from "classnames";
7 | import {
8 | Card,
9 | CardHeader,
10 | CardBody,
11 | Container,
12 | Row,
13 | Col
14 | } from "reactstrap";
15 |
16 | // Config
17 | import { config, links } from "../../../config";
18 |
19 | // API
20 | import APIFetch from "../../../util/APIFetch";
21 | import TodoAPI from "../TodoAPI";
22 |
23 | // Components
24 | import CustomButton from "../../../components/CustomButton";
25 |
26 | // Page Data
27 | import TodoViewPageData from "./TodoViewPageData";
28 |
29 | // History
30 | import History from "../../../util/History";
31 |
32 | // Toasty
33 | import Toasty from "../../../util/Toasty";
34 |
35 | /**
36 | * Todo Item View
37 | *
38 | * The Todo view contains information for all of the user's Todo list items. From here,
39 | * they are able to edit, view, and delete the individual Todo items.
40 | *
41 | * @param {Object} match Information about the view's route.
42 | * @param {String} locale The current locale of the application.
43 | * @param {Object} languageData Current language information for the app. Language data object.
44 | */
45 | const TodoView = ({ match, locale, languageData }) => {
46 | // Setup the API Fetch utility for the Todo View.
47 | const [{ isLoading, results, hasError }] = APIFetch({
48 | locale,
49 | BASEURL: config.apiServer.BASEURL,
50 | PATH_SEARCH: TodoAPI.apiService.getTodo.PATH_SEARCH,
51 | PATH_METHOD: TodoAPI.apiService.getTodo.PATH_METHOD,
52 | PATH_QUERY: `?id=${ match.params.id }`
53 | });
54 |
55 | // Create a dynamic class name for the panel that will center the text
56 | // if the result status is not 200.
57 | const isCenteredText = classNames({
58 | "mx-auto": isLoading || get(results, "status") !== 200
59 | });
60 |
61 | /**
62 | * Listen to changes in the error state.
63 | */
64 | useEffect(() => {
65 | const handleError = () => {
66 | // Check the error results and direct the user accordingly.
67 | if (get(hasError, "status") === 410) {
68 | // Let the user know the item does not exist.
69 | Toasty.notify({
70 | type: Toasty.error(),
71 | content: hasError.data.message
72 | });
73 |
74 | // Redirect the user to the proper page.
75 | History.push(links.todo.list);
76 | }
77 | }
78 |
79 | handleError();
80 | }, [hasError]);
81 |
82 | // Render the Todo Item View.
83 | return (
84 |
85 |
86 |
87 |
88 |
89 | { get(languageData, ["common", "todo", "view", "title"]) }
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
104 |
105 |
106 |
107 |
108 |
109 | );
110 | }
111 |
112 | /**
113 | * Get App State
114 | *
115 | * Get the requried state for the component from the Redux store.
116 | *
117 | * @param {Object} state Application state from Redux.
118 | */
119 | const mapStateToProps = state => {
120 | return {
121 | locale: state.language.locale,
122 | languageData: state.language.languageData
123 | }
124 | }
125 |
126 | // Export the Todo Item View.
127 | export default connect(mapStateToProps)(TodoView);
128 |
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoView/TodoViewPageData.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { get, map } from "lodash";
4 | import { Col } from "reactstrap";
5 | import { Link } from "react-router-dom";
6 |
7 | // Components
8 | import PageData from "../../../components/Util/PageData";
9 | import CustomButton from "../../../components/CustomButton";
10 |
11 | // Config
12 | import { links } from "../../../config";
13 |
14 | /**
15 | * ToDo Item Data Component
16 | *
17 | * Display the ToDo item of a given ID properly.
18 | *
19 | * @param {Boolean} isLoading Loading indication for API request.
20 | * @param {Object} results Result object from the API request.
21 | * @param {Object} hasError Error object from the API request.
22 | * @param {Object} languageData Current language information for the app. Language data object.
23 | */
24 | const TodoPageData = ({ isLoading, results, hasError, languageData }) => {
25 | // Names of the field we want to display and the value of the header for the field.
26 | const fieldNames = {
27 | _id: get(languageData, ["common", "todo", "fields", "_id"]),
28 | name: get(languageData, ["common", "todo", "fields", "name"]),
29 | description: get(languageData, ["common", "todo", "fields", "description"]),
30 | done: get(languageData, ["common", "todo", "fields", "done"]),
31 | updatedAt: get(languageData, ["common", "updatedAt"]),
32 | createdAt: get(languageData, ["common", "createdAt"])
33 | }
34 |
35 | // ToDo status indicators.
36 | const todoStatus = {
37 | true: { get(languageData, ["common", "done"]) },
38 | false: { get(languageData, ["common", "incomplete"]) }
39 | }
40 |
41 | // Display the ToDo item for the current user with the given ID if it exists.
42 | const todoInfo = get(results, ["data", "content"])
43 | ? <>
44 | { map(results.data.content, (result, key) => (
45 | fieldNames.hasOwnProperty(key) &&
46 |
47 | { fieldNames[key] }
48 | { key === "done" ? todoStatus[result] : result }
49 |
50 | ))}
51 |
52 |
53 |
54 |
55 |
56 |
57 | >
58 | : null;
59 |
60 | // Return the formatted result, with error and loading indication.
61 | return ;
67 | }
68 |
69 | // Export the ToDo Item Data Component.
70 | export default TodoPageData;
--------------------------------------------------------------------------------
/frontend/src/views/Todo/TodoView/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import TodoView from "./TodoView";
3 |
4 | // Export modules
5 | export default TodoView;
--------------------------------------------------------------------------------
/frontend/src/views/Todo/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import TodoList from "./TodoList";
3 | import TodoModify from "./TodoModify";
4 | import TodoView from "./TodoView";
5 |
6 | // Export modules
7 | export {
8 | TodoList,
9 | TodoModify,
10 | TodoView
11 | };
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/Disable/Disable2FAAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the User 2Factor Disable API.
2 | export default {
3 | apiService: {
4 | postDisable2FA: {
5 | PATH_SEARCH: "/api/user/2fa",
6 | PATH_METHOD: "delete"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/Disable/Disable2FAController.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * User 2Factor Disable Controller
3 | *
4 | * Contains functions related to handling 2FA Disable responses and actions.
5 | * Enables redirecting the user depending on the response from the API
6 | * service. Also provides alert data for the view if needed.
7 | */
8 |
9 | // Dependencies
10 | import { get } from "lodash";
11 |
12 | // Config & Application Links
13 | import { links } from "../../../../config";
14 |
15 | // Response Handler
16 | import ResponseHandler from "../../../../util/ResponseHandler";
17 |
18 | // Declare the User 2Factor Disable Controller.
19 | const Disable2FactorController = {};
20 |
21 | /**
22 | * Determine post User 2Factor Disable response action
23 | *
24 | * Function takes in the status of the User 2Factor Disable response in order to
25 | * determine the post 2FA action to be taken, whether that is to
26 | * display an error message, or to redirect the user. A promise is used
27 | * to redirect the user while reject is used to display a message.
28 | *
29 | * @param {number} response The response from the API request.
30 | * @param {Object} languageData Current language information for the app. Language data object.
31 | */
32 | Disable2FactorController.handleResponse = async (response, languageData) => {
33 | // Redirect data for the redirectable statuses.
34 | const redirectData = {
35 | "200": {
36 | redirect: links.user.profile,
37 | alert: true
38 | }
39 | };
40 | // Alert data information for statuses. All statuses in this case use
41 | // the `danger` type, so we default all statuses to this with `500` as the key.
42 | const alertData = {
43 | "500": "danger"
44 | }
45 |
46 | // Return a promise with the results.
47 | return new Promise(async (resolve, reject) => {
48 | try {
49 | // Resolve with the redirect link.
50 | resolve(await ResponseHandler.status({ response, apiMessage: true, languageData, langKey: null, redirectData, alertData }));
51 | } catch (error) {
52 | // Reject with the error object.
53 | reject(error);
54 | }
55 | });
56 | };
57 |
58 | /**
59 | * Handle User 2Factor Disable Actions
60 | *
61 | * Uses the custom Response Handler to handle User 2Factor Disable actions.
62 | *
63 | * @param {Object} responseObject Response object from the request with alert info or redirect info.
64 | * @param {Object} data Data object required for handling the action.
65 | * @param {Function} action Redux action to handle data.
66 | */
67 | Disable2FactorController.handleAction = (responseObject, data, action) => {
68 | // Use the custom Response Handler to determine what to do with the user.
69 | const response = ResponseHandler.action(responseObject, data, action);
70 |
71 | // Check if there are any errors to display.
72 | if (get(response, "type")) {
73 | // Return the error information.
74 | return response;
75 | } else {
76 | // No error, so the user was redirected.
77 | return false;
78 | }
79 | }
80 |
81 | // Export the Verify 2Factor Controller.
82 | export default Disable2FactorController;
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/Disable/Disable2FAForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColXL": 8,
4 | "inputColMD": 12,
5 | "inputColClassName": "mx-auto",
6 | "type": "text",
7 | "name": "code",
8 | "prependIcon": "shield-alt",
9 | "validation": {
10 | "required": {
11 | "langKey": [
12 | "common",
13 | "input",
14 | "code",
15 | "validation",
16 | "required"
17 | ]
18 | },
19 | "length": {
20 | "rule": 6,
21 | "langKey": [
22 | "common",
23 | "input",
24 | "code",
25 | "validation",
26 | "length"
27 | ]
28 | }
29 | }
30 | }
31 | ]
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/Disable/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Disable from "./Disable";
3 |
4 | // Export modules
5 | export default Disable;
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/Enable/Enable2FAAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the User 2Factor Enable API.
2 | export default {
3 | apiService: {
4 | postEnable2FA: {
5 | PATH_SEARCH: "/api/user/2fa",
6 | PATH_METHOD: "post"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/Enable/Enable2FAController.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * User 2Factor Enable Controller
3 | *
4 | * Contains functions related to handling 2FA Enable responses and actions.
5 | * Enables redirecting the user depending on the response from the API
6 | * service. Also provides alert data for the view if needed.
7 | */
8 |
9 | // Dependencies
10 | import { get } from "lodash";
11 |
12 | // Config & Application Links
13 | import { links } from "../../../../config";
14 |
15 | // Response Handler
16 | import ResponseHandler from "../../../../util/ResponseHandler";
17 |
18 | // Declare the User 2Factor Enable Controller.
19 | const Enable2FactorController = {};
20 |
21 | /**
22 | * Determine post User 2Factor Enable response action
23 | *
24 | * Function takes in the status of the User 2Factor Enable response in order to
25 | * determine the post 2FA action to be taken, whether that is to
26 | * display an error message, or to redirect the user. A promise is used
27 | * to redirect the user while reject is used to display a message.
28 | *
29 | * @param {number} response The response from the API request.
30 | * @param {Object} languageData Current language information for the app. Language data object.
31 | */
32 | Enable2FactorController.handleResponse = async (response, languageData) => {
33 | // Redirect data for the redirectable statuses.
34 | const redirectData = {
35 | "200": {
36 | redirect: links.user.profile,
37 | alert: true
38 | }
39 | };
40 | // Alert data information for statuses. All statuses in this case use
41 | // the `danger` type, so we default all statuses to this with `500` as the key.
42 | const alertData = {
43 | "500": "danger"
44 | }
45 |
46 | // Return a promise with the results.
47 | return new Promise(async (resolve, reject) => {
48 | try {
49 | // Resolve with the redirect link.
50 | resolve(await ResponseHandler.status({ response, apiMessage: true, languageData, langKey: null, redirectData, alertData }));
51 | } catch (error) {
52 | // Reject with the error object.
53 | reject(error);
54 | }
55 | });
56 | };
57 |
58 | /**
59 | * Handle User 2Factor Enable Actions
60 | *
61 | * Uses the custom Response Handler to handle User 2Factor Enable actions.
62 | *
63 | * @param {Object} responseObject Response object from the request with alert info or redirect info.
64 | * @param {Object} data Data object required for handling the action.
65 | * @param {Function} action Redux action to handle data.
66 | */
67 | Enable2FactorController.handleAction = (responseObject, data, action) => {
68 | // Use the custom Response Handler to determine what to do with the user.
69 | const response = ResponseHandler.action(responseObject, data, action);
70 |
71 | // Check if there are any errors to display.
72 | if (get(response, "type")) {
73 | // Return the error information.
74 | return response;
75 | } else {
76 | // No error, so the user was redirected.
77 | return false;
78 | }
79 | }
80 |
81 | // Export the Verify 2Factor Controller.
82 | export default Enable2FactorController;
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/Enable/Enable2FAForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColXL": 8,
4 | "inputColMD": 12,
5 | "inputColClassName": "mx-auto",
6 | "type": "text",
7 | "name": "code",
8 | "prependIcon": "shield-alt",
9 | "validation": {
10 | "required": {
11 | "langKey": [
12 | "common",
13 | "user",
14 | "enable2Factor",
15 | "code",
16 | "validation",
17 | "required"
18 | ]
19 | },
20 | "length": {
21 | "rule": 6,
22 | "langKey": [
23 | "common",
24 | "user",
25 | "enable2Factor",
26 | "code",
27 | "validation",
28 | "length"
29 | ]
30 | }
31 | }
32 | }
33 | ]
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/Enable/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import Enable from "./Enable";
3 |
4 | // Export modules
5 | export default Enable;
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/User2Factor.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { connect } from "react-redux";
4 | import { get } from "lodash";
5 | import classNames from "classnames";
6 | import {
7 | Card,
8 | CardHeader,
9 | CardBody,
10 | Container,
11 | Row,
12 | Col
13 | } from "reactstrap";
14 |
15 | // Components
16 | import PageData from "../../../components/Util/PageData";
17 |
18 | // Views
19 | import Enable from "./Enable";
20 | import Disable from "./Disable";
21 |
22 | // Config
23 | import { config } from "../../../config";
24 |
25 | // API
26 | import APIFetch from "../../../util/APIFetch";
27 | import User2FactorAPI from "./User2FactorAPI";
28 |
29 | /**
30 | * User 2Factor Settings View
31 | *
32 | * A container view for the Enable/Disable 2Factor pages for logged in users.
33 | *
34 | * @param {String} locale The current locale of the application.
35 | * @param {Object} languageData Current language information for the app. Language data object.
36 | */
37 | const User2Factor = ({ locale, languageData }) => {
38 | // Setup the API Fetch utility for the User 2Factor Settings View.
39 | const [{ isLoading, results, hasError }] = APIFetch({
40 | locale,
41 | BASEURL: config.apiServer.BASEURL,
42 | PATH_SEARCH: User2FactorAPI.apiService.get2FA.PATH_SEARCH,
43 | PATH_METHOD: User2FactorAPI.apiService.get2FA.PATH_METHOD
44 | });
45 |
46 | // Create a dynamic class name for the panel that will center the text
47 | // if the result status is not 200.
48 | const isCenteredText = classNames({
49 | "mx-auto": isLoading || get(results, "status") !== 200
50 | });
51 |
52 | // Render the User 2Factor Settings View.
53 | return (
54 |
55 |
56 |
57 |
58 |
59 | { get(results, ["data", "twoFA"]) === true
60 | ? get(languageData, ["common", "twoFactorDisable"])
61 | : get(languageData, ["common", "twoFactorEnable"])
62 | }
63 |
64 |
65 |
66 | { get(results, ["data"])
67 | ? get(results, ["data", "twoFA"]) === true
68 | ?
69 | :
72 | :
76 | }
77 |
78 |
79 |
80 |
81 |
82 |
83 | );
84 | }
85 |
86 | /**
87 | * Get App State
88 | *
89 | * Get the requried state for the component from the Redux store.
90 | *
91 | * @param {Object} state Application state from Redux.
92 | */
93 | const mapStateToProps = state => {
94 | return {
95 | locale: state.language.locale,
96 | languageData: state.language.languageData
97 | }
98 | }
99 |
100 | // Export the User 2Factor Settings View.
101 | export default connect(mapStateToProps)(User2Factor);
102 |
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/User2FactorAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the User 2Factor API.
2 | export default {
3 | apiService: {
4 | get2FA: {
5 | PATH_SEARCH: "/api/user/2fa",
6 | PATH_METHOD: "get"
7 | },
8 | post2FA: {
9 | PATH_SEARCH: "/api/user/2fa",
10 | PATH_METHOD: "post"
11 | }
12 | }
13 | };
--------------------------------------------------------------------------------
/frontend/src/views/User/User2Factor/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import User2Factor from "./User2Factor";
3 |
4 | // Export modules
5 | export default User2Factor;
--------------------------------------------------------------------------------
/frontend/src/views/User/UserChangePassword/UserChangePasswordAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the User Change Password API.
2 | export default {
3 | apiService: {
4 | postChangePassword: {
5 | PATH_SEARCH: "/api/user/changePassword",
6 | PATH_METHOD: "post"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/User/UserChangePassword/UserChangePasswordController.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * User Change Password Controller
3 | *
4 | * Contains functions related to handling User Change Password responses and actions.
5 | * Enables redirecting the user depending on the response from the API
6 | * service. Also provides alert data for the view if needed. This is for logged in users.
7 | */
8 |
9 | // Dependencies
10 | import { get } from "lodash";
11 |
12 | // Config & Application Links
13 | import { links } from "../../../config";
14 |
15 | // Response Handler
16 | import ResponseHandler from "../../../util/ResponseHandler";
17 |
18 | // Declare the User Change Password Controller.
19 | const UserChangePasswordController = {};
20 |
21 | /**
22 | * Determine post User Change Password response action
23 | *
24 | * Function takes in the status of the User Change Password response in order to
25 | * determine the post User Change Password action to be taken, whether that is to
26 | * display an error message, or to redirect the user. A promise is used
27 | * to redirect the user while reject is used to display a message.
28 | *
29 | * @param {number} response The response from the API request.
30 | * @param {Object} languageData Current language information for the app. Language data object.
31 | */
32 | UserChangePasswordController.handleResponse = async (response, languageData) => {
33 | // Redirect data for the redirectable statuses.
34 | const redirectData = {
35 | "200": {
36 | redirect: links.home,
37 | alert: true
38 | }
39 | };
40 | // Alert data information for statuses. All statuses in this case use
41 | // the `danger` type, so we default all statuses to this with `500` as the key.
42 | const alertData = {
43 | "500": "danger"
44 | }
45 |
46 | // Return a promise with the results.
47 | return new Promise(async (resolve, reject) => {
48 | try {
49 | // Resolve with the redirect link.
50 | resolve(await ResponseHandler.status({ response, apiMessage: true, languageData, langKey: null, redirectData, alertData }));
51 | } catch (error) {
52 | // Reject with the error object.
53 | reject(error);
54 | }
55 | });
56 | };
57 |
58 | /**
59 | * Handle User Change Password Actions
60 | *
61 | * Uses the custom Response Handler to handle User Change Password actions.
62 | *
63 | * @param {Object} responseObject Response object from the request with alert info or redirect info.
64 | * @param {Object} data Data object required for handling the action.
65 | * @param {Function} action Redux action to handle data.
66 | */
67 | UserChangePasswordController.handleAction = (responseObject, data, action) => {
68 | // Use the custom Response Handler to determine what to do with the user.
69 | const response = ResponseHandler.action(responseObject, data, action);
70 |
71 | // Check if there are any errors to display.
72 | if (get(response, "type")) {
73 | // Return the error information.
74 | return response;
75 | } else {
76 | // No error, so the user was redirected.
77 | return false;
78 | }
79 | }
80 |
81 | // Export the Change Password Controller.
82 | export default UserChangePasswordController;
--------------------------------------------------------------------------------
/frontend/src/views/User/UserChangePassword/UserChangePasswordForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColXL": 8,
4 | "inputColMD": 12,
5 | "inputColClassName": "mx-auto",
6 | "type": "password",
7 | "name": "currentPassword",
8 | "prependIcon": "user-lock",
9 | "validation": {
10 | "required": {
11 | "langKey": [
12 | "common",
13 | "input",
14 | "currentPassword",
15 | "validation",
16 | "required"
17 | ]
18 | }
19 | }
20 | },
21 | {
22 | "inputColXL": 8,
23 | "inputColMD": 12,
24 | "inputColClassName": "mx-auto",
25 | "type": "password",
26 | "name": "password",
27 | "prependIcon": "lock",
28 | "validation": {
29 | "required": {
30 | "langKey": [
31 | "common",
32 | "input",
33 | "password",
34 | "validation",
35 | "required"
36 | ]
37 | },
38 | "minLength": {
39 | "rule": 8,
40 | "langKey": [
41 | "common",
42 | "input",
43 | "password",
44 | "validation",
45 | "minLength"
46 | ]
47 | },
48 | "max": {
49 | "rule": 256,
50 | "langKey": [
51 | "common",
52 | "input",
53 | "password",
54 | "validation",
55 | "max"
56 | ]
57 | }
58 | }
59 | },
60 | {
61 | "inputColXL": 8,
62 | "inputColMD": 12,
63 | "inputColClassName": "mx-auto",
64 | "type": "password",
65 | "name": "confirmPassword",
66 | "prependIcon": "lock",
67 | "validation": {
68 | "required": {
69 | "langKey": [
70 | "common",
71 | "input",
72 | "confirmPassword",
73 | "validation",
74 | "required"
75 | ]
76 | },
77 | "confirmValue": {
78 | "langKey": [
79 | "common",
80 | "input",
81 | "confirmPassword",
82 | "validation",
83 | "confirmValue"
84 | ]
85 | }
86 | }
87 | }
88 | ]
--------------------------------------------------------------------------------
/frontend/src/views/User/UserChangePassword/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import UserChangePassword from "./UserChangePassword";
3 |
4 | // Export modules
5 | export default UserChangePassword;
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/UserProfileEdit/UserProfileEditAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the User Profile Edit API.
2 | export default {
3 | apiService: {
4 | getProfile: {
5 | PATH_SEARCH: "/api/user/profile",
6 | PATH_METHOD: "get"
7 | },
8 | putProfile: {
9 | PATH_SEARCH: "/api/user/profile",
10 | PATH_METHOD: "put"
11 | }
12 | }
13 | };
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/UserProfileEdit/UserProfileEditController.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * User Profile Edit Controller
3 | *
4 | * Contains functions related to handling User Profile Edit responses and actions.
5 | * Enables redirecting the user depending on the response from the API
6 | * service. Also provides alert data for the view if needed.
7 | */
8 |
9 | // Dependencies
10 | import { get } from "lodash";
11 |
12 | // Config & Application Links
13 | import { links } from "../../../../config";
14 |
15 | // Response Handler
16 | import ResponseHandler from "../../../../util/ResponseHandler";
17 |
18 | // Declare the User Profile Edit Controller.
19 | const UserProfileEditController = {};
20 |
21 | /**
22 | * Determine post User Profile Edit response action
23 | *
24 | * Function takes in the status of the User Profile Edit response in order to
25 | * determine the post profile save action to be taken, whether that is to
26 | * display an error message, or to redirect the user. A promise is used
27 | * to redirect the user while reject is used to display a message.
28 | *
29 | * @param {number} response The response from the API request.
30 | * @param {Object} languageData Current language information for the app. Language data object.
31 | */
32 | UserProfileEditController.handleResponse = async (response, languageData) => {
33 | // Redirect data for the redirectable statuses.
34 | const redirectData = {
35 | "200": {
36 | redirect: links.user.profile,
37 | alert: true
38 | }
39 | };
40 | // Alert data information for statuses. All statuses in this case use
41 | // the `danger` type, so we default all statuses to this with `500` as the key.
42 | const alertData = {
43 | "500": "danger"
44 | }
45 |
46 | // Return a promise with the results.
47 | return new Promise(async (resolve, reject) => {
48 | try {
49 | // Resolve with the redirect link.
50 | resolve(await ResponseHandler.status({ response, apiMessage: true, languageData, langKey: null, redirectData, alertData }));
51 | } catch (error) {
52 | // Reject with the error object.
53 | reject(error);
54 | }
55 | });
56 | };
57 |
58 | /**
59 | * Handle User Profile Edit Actions
60 | *
61 | * Uses the custom Response Handler to handle User Profile Edit actions.
62 | *
63 | * @param {Object} responseObject Response object from the request with alert info or redirect info.
64 | * @param {Object} data Data object required for handling the action.
65 | * @param {Function} action Redux action to handle data.
66 | */
67 | UserProfileEditController.handleAction = (responseObject, data, action) => {
68 | // Add additional content to the response object to be handled.
69 | responseObject = {
70 | ...responseObject,
71 | setUser: true
72 | }
73 |
74 | // Use the custom Response Handler to determine what to do with the user.
75 | const response = ResponseHandler.action(responseObject, data, action);
76 |
77 | // Check if there are any errors to display.
78 | if (get(response, "type")) {
79 | // Return the error information.
80 | return response;
81 | } else {
82 | // No error, so the user was redirected.
83 | return false;
84 | }
85 | }
86 |
87 | // Export the Verify 2Factor Controller.
88 | export default UserProfileEditController;
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/UserProfileEdit/UserProfileEditForm.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputColXL": 8,
4 | "inputColMD": 12,
5 | "inputColClassName": "mx-auto",
6 | "type": "text",
7 | "name": "firstName",
8 | "prependIcon": "user-tie",
9 | "validation": {
10 | "required": {
11 | "langKey": [
12 | "common",
13 | "input",
14 | "firstName",
15 | "validation",
16 | "required"
17 | ]
18 | },
19 | "minLength": {
20 | "rule": 3,
21 | "langKey": [
22 | "common",
23 | "input",
24 | "firstName",
25 | "validation",
26 | "minLength"
27 | ]
28 | },
29 | "max": {
30 | "rule": 50,
31 | "langKey": [
32 | "common",
33 | "input",
34 | "firstName",
35 | "validation",
36 | "max"
37 | ]
38 | }
39 | }
40 | },
41 | {
42 | "inputColXL": 8,
43 | "inputColMD": 12,
44 | "inputColClassName": "mx-auto",
45 | "type": "text",
46 | "name": "lastName",
47 | "prependIcon": "user-ninja",
48 | "validation": {
49 | "required": {
50 | "langKey": [
51 | "common",
52 | "input",
53 | "lastName",
54 | "validation",
55 | "required"
56 | ]
57 | },
58 | "minLength": {
59 | "rule": 3,
60 | "langKey": [
61 | "common",
62 | "input",
63 | "lastName",
64 | "validation",
65 | "minLength"
66 | ]
67 | },
68 | "max": {
69 | "rule": 100,
70 | "langKey": [
71 | "common",
72 | "input",
73 | "lastName",
74 | "validation",
75 | "max"
76 | ]
77 | }
78 | }
79 | },
80 | {
81 | "inputColXL": 8,
82 | "inputColMD": 12,
83 | "inputColClassName": "mx-auto",
84 | "type": "email",
85 | "name": "email",
86 | "prependIcon": "envelope",
87 | "validation": {
88 | "required": {
89 | "langKey": [
90 | "common",
91 | "input",
92 | "email",
93 | "validation",
94 | "required"
95 | ]
96 | },
97 | "email": {
98 | "langKey": [
99 | "common",
100 | "input",
101 | "email",
102 | "validation",
103 | "email"
104 | ]
105 | }
106 | }
107 | },
108 | {
109 | "inputColXL": 8,
110 | "inputColMD": 12,
111 | "inputColClassName": "mx-auto",
112 | "type": "text",
113 | "name": "username",
114 | "prependIcon": "user-secret",
115 | "validation": {
116 | "required": {
117 | "langKey": [
118 | "common",
119 | "input",
120 | "username",
121 | "validation",
122 | "required"
123 | ]
124 | },
125 | "minLength": {
126 | "rule": 5,
127 | "langKey": [
128 | "common",
129 | "input",
130 | "username",
131 | "validation",
132 | "minLength"
133 | ]
134 | },
135 | "max": {
136 | "rule": 30,
137 | "langKey": [
138 | "common",
139 | "input",
140 | "username",
141 | "validation",
142 | "max"
143 | ]
144 | }
145 | }
146 | },
147 | {
148 | "inputColXL": 8,
149 | "inputColMD": 12,
150 | "inputColClassName": "mx-auto",
151 | "type": "tel",
152 | "name": "mobilePhone",
153 | "maxLength": 10,
154 | "prependIcon": "phone",
155 | "validation": {
156 | "phoneOptional": {
157 | "langKey": [
158 | "common",
159 | "input",
160 | "mobilePhone",
161 | "validation",
162 | "phoneOptional"
163 | ]
164 | }
165 | }
166 | }
167 | ]
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/UserProfileEdit/UserProfileEditPageData.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 |
4 | // Components
5 | import PageData from "../../../../components/Util/PageData";
6 |
7 | /**
8 | * User Profile Edit Page Data Component
9 | *
10 | * Display the user's information for their profile to be able to edit it.
11 | *
12 | * @param {Boolean} isLoading Loading indication for API request.
13 | * @param {Object} results Result object from the API request.
14 | * @param {Object} hasError Error object from the API request.
15 | * @param {Function} component The component that needs to be diplayed, as a function.
16 | */
17 | const UserProfileEditPageData = ({ isLoading, results, hasError, component }) => {
18 | // Display the user's profile information from the function provided.
19 | const profileInfo = component();
20 |
21 | // Return the formatted result, with error and loading indication.
22 | return ;
28 | }
29 |
30 | // Export the ToDo Item Data Component.
31 | export default UserProfileEditPageData;
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/UserProfileEdit/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import UserProfileEdit from "./UserProfileEdit";
3 |
4 | // Export modules
5 | export default UserProfileEdit;
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/UserProfileView/UserProfileView.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { connect } from "react-redux";
4 | import { Link } from "react-router-dom";
5 | import { get } from "lodash";
6 | import classNames from "classnames";
7 | import {
8 | Card,
9 | CardHeader,
10 | CardBody,
11 | Container,
12 | Row,
13 | Col
14 | } from "reactstrap";
15 |
16 | // Config
17 | import { config, links } from "../../../../config";
18 |
19 | // API
20 | import APIFetch from "../../../../util/APIFetch";
21 | import UserProfileViewAPI from "./UserProfileViewAPI";
22 |
23 | // Components
24 | import CustomButton from "../../../../components/CustomButton";
25 |
26 | // Page Data
27 | import UserProfileViewPageData from "./UserProfileViewPageData";
28 |
29 | /**
30 | * User Profile View
31 | *
32 | * Get the user's profile information from FusionAuth.
33 | *
34 | * @param {String} locale The current locale of the application.
35 | * @param {Object} languageData Current language information for the app. Language data object.
36 | */
37 | const UserProfileView = ({ locale, languageData }) => {
38 | // Setup the API Fetch utility for the User Profile View.
39 | const [{ isLoading, results, hasError }] = APIFetch({
40 | locale,
41 | BASEURL: config.apiServer.BASEURL,
42 | PATH_SEARCH: UserProfileViewAPI.apiService.getProfile.PATH_SEARCH,
43 | PATH_METHOD: UserProfileViewAPI.apiService.getProfile.PATH_METHOD
44 | });
45 |
46 | // Create a dynamic class name for the panel that will center the text
47 | // if the result status is not 200.
48 | const isCenteredText = classNames({
49 | "mx-auto": isLoading || get(results, "status") !== 200
50 | });
51 |
52 | // Render the User Profile View.
53 | return (
54 |
55 |
56 |
57 |
58 |
59 | { get(languageData, ["common", "myProfile"]) }
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | );
79 | }
80 |
81 | /**
82 | * Get App State
83 | *
84 | * Get the requried state for the component from the Redux store.
85 | *
86 | * @param {Object} state Application state from Redux.
87 | */
88 | const mapStateToProps = state => {
89 | return {
90 | locale: state.language.locale,
91 | languageData: state.language.languageData
92 | }
93 | }
94 |
95 | // Export the User Profile View.
96 | export default connect(mapStateToProps)(UserProfileView);
97 |
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/UserProfileView/UserProfileViewAPI.jsx:
--------------------------------------------------------------------------------
1 | // Export the User Profile View API.
2 | export default {
3 | apiService: {
4 | getProfile: {
5 | PATH_SEARCH: "/api/user/profile",
6 | PATH_METHOD: "get"
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/UserProfileView/UserProfileViewPageData.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import { Col } from "reactstrap";
4 | import { map } from "lodash";
5 |
6 | // Components
7 | import PageData from "../../../../components/Util/PageData";
8 |
9 | /**
10 | * User Profile View Page Data Component
11 | *
12 | * Display the user's profile data properly.
13 | *
14 | * @param {boolean} isLoading Loading indication for API request.
15 | * @param {object} results Result object from the API request.
16 | * @param {object} hasError Error object from the API request.
17 | */
18 | const UserProfileViewPageData = ({ isLoading, results, hasError }) => {
19 | // Fields from the user object that we care about. The rest are not
20 | // wanted or needed to be seen.
21 | const fieldNames = [
22 | { fieldName: "firstName", text: "First Name" },
23 | { fieldName: "lastName", text: "Last Name"},
24 | { fieldName: "email", text: "Email" },
25 | { fieldName: "username", text: "Username" },
26 | { fieldName: "twoFactorEnabled", text: "2FA Enabled" },
27 | { fieldName: "mobilePhone", text: "Phone" }
28 | ];
29 |
30 | // Display user information if the results object is set.
31 | // Loop through and find information in the result object from the fields array.
32 | const userInfo = results && map(fieldNames, (field, key) => {
33 | // Get the correct value.
34 | const value = results.data.content[field.fieldName] === true ? "true" : results.data.content[field.fieldName];
35 |
36 | // Return the formatted component.
37 | return (
38 |
39 | { field.text }
40 | { value || "Not Set" }
41 |
42 | );
43 | });
44 |
45 | // Return the formatted result, with error and loading indication.
46 | return ;
52 | }
53 |
54 | // Export the User Profile View Page Data Component.
55 | export default UserProfileViewPageData;
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/UserProfileView/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import UserProfileView from "./UserProfileView";
3 |
4 | // Export modules
5 | export default UserProfileView;
--------------------------------------------------------------------------------
/frontend/src/views/User/UserProfile/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import UserProfileView from "./UserProfileView";
3 | import UserProfileEdit from "./UserProfileEdit";
4 |
5 | // Export modules
6 | export {
7 | UserProfileView,
8 | UserProfileEdit
9 | };
--------------------------------------------------------------------------------
/frontend/src/views/User/index.jsx:
--------------------------------------------------------------------------------
1 | // Import modules
2 | import UserChangePassword from "./UserChangePassword";
3 | import User2Factor from "./User2Factor";
4 |
5 | // Export modules
6 | export {
7 | UserChangePassword,
8 | User2Factor
9 | };
--------------------------------------------------------------------------------
/mongodb/roles.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "route": "/user/profile/",
4 | "create": ["member"],
5 | "read": ["member"],
6 | "update": ["member"],
7 | "delete": ["member"]
8 | },
9 | {
10 | "route": "/user/profile/edit/",
11 | "create": ["member"],
12 | "read": ["member"],
13 | "update": ["member"],
14 | "delete": ["member"]
15 | },
16 | {
17 | "route": "/user/changePassword/",
18 | "create": ["member"],
19 | "read": ["member"],
20 | "update": ["member"],
21 | "delete": ["member"]
22 | },
23 | {
24 | "route": "/user/2fa/",
25 | "create": ["member"],
26 | "read": ["member"],
27 | "update": ["member"],
28 | "delete": ["member"]
29 | },
30 | {
31 | "route": "/todo/",
32 | "create": ["member"],
33 | "read": ["member"],
34 | "update": ["member"],
35 | "delete": ["member"]
36 | },
37 | {
38 | "route": "/todo/add/",
39 | "create": ["member"],
40 | "read": ["member"],
41 | "update": ["member"],
42 | "delete": ["member"]
43 | },
44 | {
45 | "route": "/todo/edit/:id",
46 | "create": ["member"],
47 | "read": ["member"],
48 | "update": ["member"],
49 | "delete": ["member"]
50 | },
51 | {
52 | "route": "/todo/:id",
53 | "create": ["member"],
54 | "read": ["member"],
55 | "update": ["member"],
56 | "delete": ["member"]
57 | }
58 | ]
--------------------------------------------------------------------------------
/server/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .env
--------------------------------------------------------------------------------
/server/.drone.yaml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | name: default
3 |
4 | steps:
5 | - name: docker
6 | image: plugins/docker
7 | settings:
8 | username:
9 | from_secret: username
10 | password:
11 | from_secret: password
12 | repo:
13 | from_secret: repo
14 | registry:
15 | from_secret: registry
16 | tags: latest
--------------------------------------------------------------------------------
/server/.example.env:
--------------------------------------------------------------------------------
1 | ## General
2 | NODE_ENV = "production"
3 | HTTP_PORT = 5000
4 | HTTPS_PORT = 5001
5 |
6 | ## Cookies
7 | COOKIES_SIGNED_SECRET = "random-uuid"
8 |
9 | ## MongoDB
10 | DATABASE_USERNAME = "demo"
11 | DATABASE_PASSWORD = "demoPass"
12 | DATABASE_HOST = "something.mongodb.net"
13 | DATABASE_NAME = "fusionAuthDemo"
14 |
15 | ## FusionAuth
16 | FUSIONAUTH_API_KEY = "Q3t_29GMZgyh_mko2rVuQ_s2qjlsZFJyJh0XirIiGVw"
17 | FUSIONAUTH_APPLICATION_ID = "10e4d908-7655-44af-abf0-1a031aff519a"
18 | FUSIONAUTH_APPLICATION_SECRET = "GUG8Hi7YzCbKUgDCRKGpPzqOrFckbNitmsYc6nGesgs"
19 |
20 | ## App Locations
21 | FUSIONAUTH_BASEURL = "http://localhost:9011"
22 | FRONTEND_BASEURL = "http://localhost:3000"
--------------------------------------------------------------------------------
/server/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: docker:latest
2 | services:
3 | - docker:18.09-dind
4 |
5 | stages:
6 | - build
7 | - deploy
8 |
9 | variables:
10 | DOCKER_HOST: tcp://localhost:2375
11 | REPO_URL: docker-repo.com/fusionauth/
12 | CONTAINER_IMAGE: service/server:${CI_COMMIT_SHORT_SHA}
13 | DEPLOYMENT_NAME: fusionauth-demo-server
14 | DEPLOYMENT_NAMESPACE: fusionauth-demo
15 |
16 | build:
17 | stage: build
18 | script:
19 | - docker login -u ${FUSIONAUTH_HARBOR_USER} -p ${FUSIONAUTH_HARBOR_PASSWORD} ${REPO_URL}
20 | - docker build -t ${CONTAINER_IMAGE} .
21 | - docker tag ${CONTAINER_IMAGE} ${REPO_URL}${CONTAINER_IMAGE}
22 | - docker push ${REPO_URL}${CONTAINER_IMAGE}
23 |
24 | deploy:
25 | stage: deploy
26 | image:
27 | name: bitnami/kubectl:latest
28 | entrypoint: [""]
29 | script:
30 | - kubectl set image deployment/${DEPLOYMENT_NAME} ${DEPLOYMENT_NAME}=${REPO_URL}${CONTAINER_IMAGE} -n ${DEPLOYMENT_NAMESPACE}
31 |
--------------------------------------------------------------------------------
/server/Dockerfile:
--------------------------------------------------------------------------------
1 | #####################
2 | ## Stage 1 - Build ##
3 | #####################
4 | # Use latest LTS node
5 | FROM node:10-alpine
6 |
7 | # Install app dependencies
8 | # A wildcard is used to ensure both package.json AND package-lock.json are copied
9 | # where available (npm@5+)
10 | COPY package*.json ./
11 |
12 | # Storing node modules on another layer will prevent unnecessary npm installs each build
13 | RUN npm ci --only=production && mkdir /app && mv ./node_modules ./app
14 |
15 | # Set working directory.
16 | WORKDIR /app
17 |
18 | # Add app to directory
19 | COPY . .
20 |
21 | # Expose APP ports
22 | EXPOSE 5000
23 | EXPOSE 5001
24 |
25 | # Run the Node application
26 | CMD ["node", "server.js"]
--------------------------------------------------------------------------------
/server/Jenkinsfile:
--------------------------------------------------------------------------------
1 | podTemplate(label: "mypod", containers: [
2 | containerTemplate(name: "docker", image: "docker", ttyEnabled: true, command: "cat"),
3 | containerTemplate(name: "node", image: "node", ttyEnabled: true, command: "cat"),
4 | containerTemplate(name: "kubectl", image: "lachlanevenson/k8s-kubectl:v1.7.3", command: "cat", ttyEnabled: true)
5 | ],
6 | volumes: [
7 | hostPathVolume(mountPath: "/var/run/docker.sock", hostPath: "/var/run/docker.sock"),
8 | ]) {
9 | node("mypod") {
10 | def commitID
11 | def imageTag
12 | def imageName = "fusionauth/service/server"
13 | def repoURL = "docker-repo.com/fusionauth/"
14 | def deploymentName = "fusionauth-demo-server"
15 | def namespace = "fusionauth-demo"
16 |
17 | stage ("Checkout Code") {
18 | checkout scm
19 | commitID = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
20 | imageTag = "${imageName}:${commitID}"
21 | }
22 |
23 | container("node") {
24 | stage("Test and Build") {
25 | // parallel(
26 | // "Run Tests": {
27 | // sh "chmod +x ./jenkins/test.sh"
28 | // sh "./jenkins/test.sh"
29 | // },
30 | // "Create Build Artifacts": {
31 | // sh "npm run build"
32 | // }
33 | // )
34 | }
35 | }
36 |
37 | container("docker") {
38 | stage("Build image") {
39 | withDockerRegistry([credentialsId: "credentials-id", url: "https://${repoURL}"]) {
40 | sh "docker build --network=host -t ${imageTag} ."
41 | sh "docker tag ${imageTag} ${repoURL}${imageTag}"
42 | sh "docker push ${repoURL}${imageTag}"
43 | }
44 | }
45 | }
46 |
47 | container("kubectl") {
48 | stage("Deploy") {
49 | withCredentials([[$class: "UsernamePasswordMultiBinding", credentialsId: "credentials-id",
50 | usernameVariable: "DOCKER_HUB_USER",
51 | passwordVariable: "DOCKER_HUB_PASSWORD"]]) {
52 | sh "kubectl set image deployment/${deploymentName} ${deploymentName}=${repoURL}${imageTag} -n ${namespace}"
53 | }
54 | }
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # FusionAuth NodeJS Example
2 |
3 | This is the NodeJS portion of the FusionAuth NodeJS + React Todo Example.
4 |
5 | ## About
6 | The NodeJS application uses Mongoose as an ORM for interacting with the MongoDB database. Express is used to serve an HTTP server, but an HTTPS server can easily be created as well if you provision an SSL certificate. The code is available for an HTTPS server (`src/expressServer/expressServer.js`), but the HTTPS server is never started in this demo.
7 |
8 | ## Prerequisites
9 | * Have already installed dependencies with `npm install`.
10 |
11 | ## Configuration
12 |
13 | Configuration is done in `.env`. You can copy or rename `.example.env` as a base. If you change the port, you will need to change it in the Dockerfile and Kubernetes configuration, if you deploy via that route.
14 |
15 | | Parameter | Description | Example |
16 | | ------------- | ------------- | ----- |
17 | | **General** |
18 | | NODE_ENV | The environment for the application to run on. | `production` |
19 | | HTTP_PORT | The port for the HTTP server to expose itself on. | `5000` |
20 | | HTTPS_PORT | The port for the HTTPS server to expose itself on. | `5001` |
21 | | **COOKIES** |
22 | | COOKIES_SIGNED_SECRET | A secret value to be used for signing and validating cookies used for FusionAuth authentication. | You can use something like [uuid](https://www.npmjs.com/package/uuid). |
23 | | **MongoDB** |
24 | | DATABASE_USERNAME | The name of the user created on the MongoDB database with read and write access for the application. | `demo` |
25 | | DATABASE_PASSWORD | The password for the MongoDB user. | `demoPass` |
26 | | DATABASE_HOST | The hostname of the location of the MongoDB database. Can also be ip:port or domain:port. | `something.mongodb.net` |
27 | | DATABASE_NAME | A database name for the application to use on the connection. | `fusionAuthDemo` |
28 | | **FusionAuth** |
29 | | FUSIONAUTH_API_KEY | The API key created in FusionAuth that provides access to resources in FusionAuth. | `Q3t_29GMZgyh_mko2rVuQ_s2qjlsZFJyJh0XirIiGVw` |
30 | | FUSIONAUTH_APPLICATION_ID | The generated application ID for the app created in the FusionAuth client for the demo. | `10e4d908-7655-44af-abf0-1a031aff519a` |
31 | | FUSIONAUTH_APPLICATION_SECRET | The generated application secret for the app created in the FusionAuth client for the demo. | `GUG8Hi7YzCbKUgDCRKGpPzqOrFckbNitmsYc6nGesgs` |
32 | | **App Locations** |
33 | | FUSIONAUTH_BASEURL | The URL where the FusionAuth server is hosted and set up. | `http://localhost:9011` |
34 | | FRONTEND_BASEURL | The URL of where the frontend is hosted so that it can be whitelisted and allowed access to the API. | `http://localhost:3000` |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fusionauth-nodejs-react-example",
3 | "description": "An example application using NodeJS, ExpressJS, ReactJS and FusionAuth",
4 | "version": "1.0.0",
5 | "author": "FusionAuth",
6 | "license": "Apache-2.0",
7 | "private": true,
8 | "main": "server.js",
9 | "contributors": [
10 | {
11 | "name": "Tyler Engelhardt",
12 | "url": "https://mainelysoftware.com",
13 | "email": "tyler@mainelysoftware.com"
14 | }
15 | ],
16 | "scripts": {
17 | "start": "node server.js",
18 | "test": "mocha"
19 | },
20 | "bugs": {
21 | "url": "https://github.com/FusionAuth/fusionauth-nodejs-react-example/issues"
22 | },
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/FusionAuth/fusionauth-nodejs-react-example"
26 | },
27 | "dependencies": {
28 | "axios": "^0.19.0",
29 | "body-parser": "^1.19.0",
30 | "cookie-parser": "^1.4.4",
31 | "cors": "^2.8.5",
32 | "express": "^4.17.1",
33 | "lodash": "^4.17.15",
34 | "mongoose": "^5.7.5",
35 | "passport": "^0.4.0"
36 | },
37 | "devDependencies": {
38 | "dotenv": "^8.1.0"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Entry point for the API
3 | *
4 | * This file brings in the express server and starts it when the app is initialized.
5 | * With this setup, you can also start background processes when the app starts.
6 | */
7 |
8 | // Dependencies
9 | const expressServer = require("./src/expressServer");
10 |
11 | // Declare the application module.
12 | const app = {};
13 |
14 | /**
15 | * Application Initialization
16 | *
17 | * The application is initialized in this function. In this case, the application starts the
18 | * express server. This can be used to start background processes as well.
19 | */
20 | app.init = () => {
21 | // Start the express server.
22 | expressServer.init();
23 | }
24 |
25 | // Start the application.
26 | app.init();
--------------------------------------------------------------------------------
/server/src/components/fusionAuth/fusionAuthAPI.js:
--------------------------------------------------------------------------------
1 | // Export the FusionAuth API.
2 | module.exports = {
3 | fusionAuth: {
4 | postRegistration: {
5 | PATH_SEARCH: "/api/user/registration",
6 | PATH_METHOD: "post"
7 | },
8 | verifyEmail: {
9 | PATH_SEARCH: "/api/user/verify-email",
10 | PATH_METHOD: "post"
11 | },
12 | verify2Factor: {
13 | PATH_SEARCH: "/api/two-factor/login",
14 | PATH_METHOD: "post"
15 | },
16 | login: {
17 | PATH_SEARCH: "/api/login",
18 | PATH_METHOD: "post"
19 | },
20 | forgotPassword: {
21 | PATH_SEARCH: "/api/user/forgot-password",
22 | PATH_METHOD: "post"
23 | },
24 | changePassword: {
25 | PATH_SEARCH: "/api/user/change-password",
26 | PATH_METHOD: "post"
27 | }
28 | }
29 | };
--------------------------------------------------------------------------------
/server/src/components/fusionAuth/fusionAuthInputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "register": {
3 | "username": {
4 | "required": true,
5 | "minLength": {
6 | "rule": 5
7 | },
8 | "max": {
9 | "rule": 30
10 | }
11 | },
12 | "firstName": {
13 | "required": true,
14 | "minLength": {
15 | "rule": 3
16 | },
17 | "max": {
18 | "rule": 50
19 | }
20 | },
21 | "lastName": {
22 | "required": true,
23 | "minLength": {
24 | "rule": 3
25 | },
26 | "max": {
27 | "rule": 100
28 | }
29 | },
30 | "email": {
31 | "required": true,
32 | "email": true
33 | },
34 | "password": {
35 | "required": true,
36 | "minLength": {
37 | "rule": 8
38 | },
39 | "max": {
40 | "rule": 256
41 | }
42 | }
43 | },
44 | "verifyEmail": {
45 | "verificationId": {
46 | "length": {
47 | "rule": 43
48 | }
49 | }
50 | },
51 | "verify2Factor": {
52 | "twoFactorId": {
53 | "required": true,
54 | "length": {
55 | "rule": 43
56 | }
57 | },
58 | "code": {
59 | "required": true,
60 | "length": {
61 | "rule": 6
62 | }
63 | }
64 | },
65 | "forgotPassword": {
66 | "loginId": {
67 | "required": true
68 | }
69 | },
70 | "changePassword": {
71 | "password": {
72 | "required": true,
73 | "minLength": {
74 | "rule": 8
75 | },
76 | "max": {
77 | "rule": 256
78 | }
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/server/src/components/fusionAuth/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const fusionAuth = require("./fusionAuth");
3 |
4 | // Export modules
5 | module.exports = fusionAuth;
--------------------------------------------------------------------------------
/server/src/components/health/health.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Health Module
3 | *
4 | * This module contains functions for the /health endpoint.
5 | */
6 |
7 | // Dependencies
8 | const express = require("express");
9 | const router = express.Router();
10 | const { get } = require("lodash");
11 |
12 | // Language
13 | const language = require("../../util/language");
14 |
15 | /**
16 | * Health Check
17 | *
18 | * Returns a 200 status ok so services know the API is available.
19 | */
20 | router.get("/", (req, res) => {
21 | // Language data.
22 | const languageData = language.getText(req.headers.locale);
23 |
24 | // Send a 200 status ok response with text saying the API is available. Defaults
25 | // to an English message.
26 | res.send({ message: get(languageData, ["common", "app", "available"]) });
27 | });
28 |
29 | // Export the User API module.
30 | module.exports = router;
31 |
--------------------------------------------------------------------------------
/server/src/components/health/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const health = require("./health");
3 |
4 | // Export modules
5 | module.exports = health;
--------------------------------------------------------------------------------
/server/src/components/roles/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const roles = require("./roles");
3 | const rolesModel = require("./rolesModel");
4 |
5 | // Export modules
6 | module.exports = {
7 | roles,
8 | rolesModel
9 | };
--------------------------------------------------------------------------------
/server/src/components/roles/roles.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Roles API Module
3 | *
4 | * This module contains functions for the /api/roles endpoints. Allows for
5 | * ensuring users have access to the actions they are trying to perform.
6 | */
7 |
8 | // Dependencies
9 | const express = require("express");
10 | const router = express.Router();
11 | const { get } = require("lodash");
12 |
13 | // Config
14 | const config = require("../../config");
15 |
16 | // Model
17 | const rolesModel = require("./rolesModel");
18 |
19 | // Language
20 | const language = require("../../util/language");
21 |
22 | /**
23 | * Determine page access
24 | *
25 | * This is a method the frontend uses to determine whether or not a user is
26 | * able to view a page in the application.
27 | */
28 | router.post("/canAccessPage", (req, res) => {
29 | // Language data.
30 | const languageData = language.getText(req.headers.locale);
31 | // Grab the user registrations from the request object set in the middleware.
32 | const registrations = req.user.registrations;
33 | // Filter out the applications that aren't the one specified in the config.
34 | const application = registrations.filter(registration => registration.applicationId === config.fusionAuth.applicationId);
35 | // There should only be one object in the resulting array, since IDs are unique. Grab the
36 | // roles from that object.
37 | const userRoles = application[0].roles;
38 |
39 | // Use the Roles Model to find the route in question, sent in the body of the request.
40 | rolesModel
41 | .findOne({ route: req.body.path })
42 | .then(route => {
43 | // Loop through the read roles to see if the user has one of them.
44 | for (const allowedReadRole of route.read) {
45 | // Check if the looped role is included in the user's selection of roles.
46 | if (userRoles.includes(allowedReadRole)) {
47 | // Send a 200 OK result if the user has one of the allowed read roles for the page.
48 | res.send();
49 | // Return, stop looping.
50 | return;
51 | }
52 | }
53 |
54 | // Send a 401 Unauthorized error if the user does not have access.
55 | res.status(401).send({ message: get(languageData, ["common", "roles", "noAccess"]) });
56 | })
57 | .catch(() => {
58 | // There was an error in the request, so forbid the user from accessing the content.
59 | res.status(403).send({ message: get(languageData, ["common", "roles", "notAuthorized"]) });
60 | });
61 | });
62 |
63 | // Export the User API module.
64 | module.exports = router;
65 |
--------------------------------------------------------------------------------
/server/src/components/roles/rolesModel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Roles Model
3 | *
4 | * This sets up the Roles Model for the application. Roles are utilized
5 | * to determine user access to view, add, edit, or delete content for different
6 | * pages. The unique identifier for the roles is the route name being accessed.
7 | */
8 |
9 | // Dependencies
10 | const mongoose = require("mongoose")
11 |
12 | // Schema for the Roles Model.
13 | const roleSchema = new mongoose.Schema({
14 | route: {
15 | type: String,
16 | index: true,
17 | unique: true
18 | },
19 | create: [String],
20 | read: [String],
21 | update: [String],
22 | delete: [String]
23 | });
24 |
25 | // Setup indexes
26 | roleSchema.index({ route: 1 }, { unique: true });
27 |
28 | // Export the Roles Model.
29 | module.exports = mongoose.model("role", roleSchema)
--------------------------------------------------------------------------------
/server/src/components/todo/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const todo = require("./todo");
3 | const todoModel = require("./todoModel");
4 |
5 | // Export modules
6 | module.exports = {
7 | todo,
8 | todoModel
9 | };
--------------------------------------------------------------------------------
/server/src/components/todo/todoInputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "required": true,
4 | "minLength": {
5 | "rule": 5
6 | },
7 | "max": {
8 | "rule": 150
9 | }
10 | },
11 | "description": {
12 | "required": true,
13 | "minLength": {
14 | "rule": 5
15 | }
16 | },
17 | "done": {
18 | "boolean": true
19 | }
20 | }
--------------------------------------------------------------------------------
/server/src/components/todo/todoModel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Todo Model
3 | *
4 | * This sets up the Todo Model for the application.
5 | */
6 |
7 | // Dependencies
8 | const mongoose = require("mongoose")
9 |
10 | // Schema for the Roles Model.
11 | const todoSchema = new mongoose.Schema({
12 | userID: String,
13 | name: {
14 | type: String,
15 | index: true,
16 | required: true
17 | },
18 | description: String,
19 | done: Boolean,
20 | createdAt: { type: Date, default: Date.now },
21 | updatedAt: { type: Date, default: Date.now }
22 | });
23 |
24 | // Setup pre-use handler for the update one function.
25 | todoSchema.pre("updateOne", function (next) {
26 | // Set the updated field to the current datetime.
27 | this.update({},{ $set: { updatedAt: new Date() } });
28 |
29 | // Continue in the model.
30 | next();
31 | });
32 |
33 | // Export the Todo Model.
34 | module.exports = mongoose.model("todo", todoSchema)
--------------------------------------------------------------------------------
/server/src/components/user/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const user = require("./user");
3 |
4 | // Export modules
5 | module.exports = user;
--------------------------------------------------------------------------------
/server/src/components/user/userAPI.js:
--------------------------------------------------------------------------------
1 | // Export the User API.
2 | module.exports = {
3 | fusionAuth: {
4 | getUser: {
5 | PATH_SEARCH: "/api/user",
6 | PATH_METHOD: "get"
7 | },
8 | login: {
9 | PATH_SEARCH: "/api/login",
10 | PATH_METHOD: "post"
11 | },
12 | changePassword: {
13 | PATH_SEARCH: "/api/user/change-password",
14 | PATH_METHOD: "post"
15 | },
16 | get2Factor: {
17 | PATH_SEARCH: "/api/two-factor/secret",
18 | PATH_METHOD: "get"
19 | },
20 | postEnable2Factor: {
21 | PATH_SEARCH: "/api/user/two-factor",
22 | PATH_METHOD: "post"
23 | },
24 | deleteDisable2Factor: {
25 | PATH_SEARCH: "/api/user/two-factor",
26 | PATH_METHOD: "delete"
27 | },
28 | saveProfile: {
29 | PATH_SEARCH: "/api/user",
30 | PATH_METHOD: "put"
31 | }
32 | }
33 | };
--------------------------------------------------------------------------------
/server/src/components/user/userInputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "changePassword": {
3 | "currentPassword": {
4 | "required": true
5 | },
6 | "password": {
7 | "required": true,
8 | "minLength": {
9 | "rule": 8
10 | },
11 | "max": {
12 | "rule": 256
13 | }
14 | }
15 | },
16 | "enable2Factor": {
17 | "code": {
18 | "required": true,
19 | "length": {
20 | "rule": 6
21 | }
22 | },
23 | "secret": {
24 | "required": true,
25 | "length": {
26 | "rule": 6
27 | }
28 | }
29 | },
30 | "disable2Factor": {
31 | "code": {
32 | "required": true,
33 | "length": {
34 | "rule": 6
35 | }
36 | }
37 | },
38 | "saveProfile": {
39 | "username": {
40 | "required": true,
41 | "minLength": {
42 | "rule": 5
43 | },
44 | "max": {
45 | "rule": 30
46 | }
47 | },
48 | "firstName": {
49 | "required": true,
50 | "minLength": {
51 | "rule": 3
52 | },
53 | "max": {
54 | "rule": 50
55 | }
56 | },
57 | "lastName": {
58 | "required": true,
59 | "minLength": {
60 | "rule": 3
61 | },
62 | "max": {
63 | "rule": 100
64 | }
65 | },
66 | "email": {
67 | "required": true,
68 | "email": true
69 | },
70 | "mobilePhone": {
71 | "phoneOptional": true
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/server/src/config/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Config Module
3 | *
4 | * The application's config is exported here using the environment variables
5 | * passed to the application. Use docker compose files, .env files, or
6 | * kubernetes config files to store managed environments.
7 | */
8 |
9 | // Comment out or remove the following line if you wish to deploy via a Docker image.
10 | // This is set here to allow for using a .env file.
11 | require('dotenv').config();
12 |
13 | // Create the config module.
14 | const config = {
15 | // Environment name
16 | envName: process.env.NODE_ENV,
17 | // HTTP Port: Used for creating a HTTP server.
18 | httpPort: process.env.HTTP_PORT,
19 | // HTTPS Port: Used for creating a HTTPS server (not used in this demo).
20 | httpsPort: process.env.HTTPS_PORT,
21 | cookies: {
22 | // UUID or Random string for cookie secret
23 | signedSecret: process.env.COOKIES_SIGNED_SECRET
24 | },
25 | database: {
26 | // Database host: ip:port or server-name.tld:port
27 | host: process.env.DATABASE_HOST,
28 | // Database usename
29 | user: process.env.DATABASE_USERNAME,
30 | // Database user password
31 | password: process.env.DATABASE_PASSWORD,
32 | // MongoDB Database name
33 | database: process.env.DATABASE_NAME
34 | },
35 | fusionAuth: {
36 | // Application API Key from FusionAuth
37 | apiKey: process.env.FUSIONAUTH_API_KEY,
38 | // Application ID from FusionAuth
39 | applicationId: process.env.FUSIONAUTH_APPLICATION_ID,
40 | // Application Secret from FusionAuth
41 | applicationSecret: process.env.FUSIONAUTH_APPLICATION_SECRET,
42 | // FusionAuth URL: http://localhost:9011
43 | baseURL: process.env.FUSIONAUTH_BASEURL
44 | },
45 | frontend: {
46 | // React APP URL: http://localhost:3000
47 | baseURL: process.env.FRONTEND_BASEURL
48 | }
49 | };
50 |
51 | // Export the config.
52 | module.exports = config;
--------------------------------------------------------------------------------
/server/src/config/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const config = require("./config");
3 |
4 | // Export modules
5 | module.exports = config;
--------------------------------------------------------------------------------
/server/src/data/fusionAuth/fusionAuthData.js:
--------------------------------------------------------------------------------
1 | /**
2 | * FusionAuth Data
3 | *
4 | * FusionAuth Data information is here in regards to API messages
5 | * and the content that correlates with the messages.
6 | */
7 |
8 | // Create the FusionAuth Data module.
9 | const fusionAuthData = {
10 | "[previouslyUsed]user.password": 0,
11 | "[tooYoung]user.password": 0,
12 | "[tooShort]user.password": 8,
13 | "[tooLong]user.password": 256
14 | };
15 |
16 | // Export the FusionAuth Data module.
17 | module.exports = fusionAuthData;
--------------------------------------------------------------------------------
/server/src/data/fusionAuth/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const fusionAuthData = require("./fusionAuthData");
3 |
4 | // Export modules
5 | module.exports = fusionAuthData;
--------------------------------------------------------------------------------
/server/src/data/language/en/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const common = require("./common");
3 |
4 | // Export modules
5 | module.exports = {
6 | common
7 | };
--------------------------------------------------------------------------------
/server/src/data/language/es/common.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FusionAuth/fusionauth-nodejs-react-example/b0a2ba366c1d7c4fbcd507a296a0845d072221a6/server/src/data/language/es/common.json
--------------------------------------------------------------------------------
/server/src/data/language/es/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const common = require("./common");
3 |
4 | // Export modules
5 | module.exports = {
6 | common
7 | };
--------------------------------------------------------------------------------
/server/src/database/database.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Database Module
3 | *
4 | * Handle database actions for the application.
5 | */
6 |
7 | // Dependencies
8 | const mongoose = require("mongoose");
9 |
10 | // Config
11 | const config = require("../config");
12 |
13 | // Shorthand for database proprties.
14 | const db = config.database;
15 |
16 | // Create the Database Module.
17 | const database = {};
18 |
19 | /**
20 | * Connect to the database
21 | *
22 | * Attempt to connect to the database with the credentials and database provided. Make
23 | * sure we use "useNewUrlParser" in the options due to Mongo v4.
24 | */
25 | database.connect = () => {
26 | // Return a promise (resolved) after a successful connection, or reject
27 | // if there was an error connecting to the database.
28 | return new Promise((resolve, reject) => {
29 | mongoose.connect(`mongodb+srv://${ db.user }:${ db.password }@${ db.host }/${ db.database }`, { useCreateIndex: true, useNewUrlParser: true })
30 | .then(() => resolve())
31 | .catch(() => reject());
32 | });
33 | };
34 |
35 | // Export the Database Module.
36 | module.exports = database;
--------------------------------------------------------------------------------
/server/src/database/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const mongodb = require("./database");
3 |
4 | // Export modules
5 | module.exports = mongodb;
--------------------------------------------------------------------------------
/server/src/expressServer/expressAPI.js:
--------------------------------------------------------------------------------
1 | // Export the Express API.
2 | module.exports = {
3 | fusionAuth: {
4 | validateToken: {
5 | PATH_SEARCH: "/api/jwt/validate",
6 | PATH_METHOD: "get"
7 | },
8 | refreshToken: {
9 | PATH_SEARCH: "/api/jwt/refresh",
10 | PATH_METHOD: "post"
11 | },
12 | getUser: {
13 | PATH_SEARCH: "/api/user",
14 | PATH_METHOD: "get"
15 | }
16 | }
17 | };
--------------------------------------------------------------------------------
/server/src/expressServer/expressRoutes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Express Routes Module
3 | *
4 | * The route module handles all incoming requests to the server and routes appropriately
5 | * to the correct route functions.
6 | */
7 |
8 | // Middleware
9 | const expressMiddleware = require("./expressMiddleware");
10 |
11 | // Route info
12 | const health = require('../components/health');
13 | const fusionAuth = require('../components/fusionAuth');
14 | const { roles } = require("../components/roles");
15 | const user = require('../components/user');
16 | const { todo } = require('../components/todo');
17 |
18 | /**
19 | * Route Handler
20 | *
21 | * The function handles all routes and passes requests to their
22 | * appropriate route handler.
23 | *
24 | * @param {Object} app The express application
25 | */
26 | const routes = app => {
27 | // Health check
28 | app.use("/health", health);
29 |
30 | // Authentication routes first.
31 | app.use("/api/fusionAuth", fusionAuth);
32 | // Validate JWT tokens.
33 | app.use(expressMiddleware.jwtValidate);
34 | // Grab the current user's info to ensure accurary.
35 | app.use(expressMiddleware.getCurrentUser);
36 | // Determine if a user can access a given page.
37 | app.use("/api/roles", roles);
38 | // Rest of the API endpoints.
39 | app.use("/api/user", user);
40 | app.use("/api/todo", todo);
41 | }
42 |
43 | // Export the express routes module.
44 | module.exports = routes;
--------------------------------------------------------------------------------
/server/src/expressServer/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const expressServer = require("./expressServer");
3 |
4 | // Export modules
5 | module.exports = expressServer;
--------------------------------------------------------------------------------
/server/src/util/apiFetch/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const apiFetch = require("./apiFetch");
3 |
4 | // Export modules
5 | module.exports = apiFetch;
--------------------------------------------------------------------------------
/server/src/util/auth/auth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Auth Module
3 | *
4 | * This module handles authentication requirements for the API Service.
5 | */
6 |
7 | // Declare the Auth module
8 | const auth = {};
9 |
10 | /**
11 | * Set refresh and access tokens
12 | *
13 | * Allows refresh and access tokens to be set so that the user has persisted access
14 | * to the application.
15 | *
16 | * @param {String} refreshToken FA refresh token.
17 | * @param {String} accessToken FA access token.
18 | * @param {Object} res The response object of the request.
19 | */
20 | auth.setCookies = (refreshToken, accessToken, res) => {
21 | // Cookie settings. In a production application, you want to set
22 | // secure: true, and if possible, sameSite: true/strict.
23 | const cookieSettings = { httpOnly: true, signed: true };
24 |
25 | // Set the JWT and refresh cookies.
26 | res.cookie("refresh_token", refreshToken, cookieSettings);
27 | res.cookie("access_token", accessToken, cookieSettings);
28 | }
29 |
30 | /**
31 | * Remove refresh and access tokens
32 | *
33 | * Removes the access and refresh token so that the user will no longer have access
34 | * to the application.
35 | *
36 | * @param {Object} res The response object of the request.
37 | */
38 | auth.removeCookies = (res) => {
39 | // Cookie settings
40 | const cookieSettings = { httpOnly: true, signed: true };
41 |
42 | // Remove the JWT and refresh cookies.
43 | res.clearCookie("refresh_token", cookieSettings);
44 | res.clearCookie("access_token", cookieSettings);
45 | }
46 |
47 | // Export the Auth module.
48 | module.exports = auth;
--------------------------------------------------------------------------------
/server/src/util/auth/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const auth = require("./auth");
3 |
4 | // Export modules
5 | module.exports = auth;
--------------------------------------------------------------------------------
/server/src/util/language/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const language = require("./language");
3 |
4 | // Export modules
5 | module.exports = language;
--------------------------------------------------------------------------------
/server/src/util/language/language.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Language Module
3 | *
4 | * This module will get language data for the API endpoints.
5 | */
6 |
7 | // Declare the language module.
8 | const language = {};
9 |
10 | /**
11 | * Get text from the language files
12 | *
13 | * Get the proper text for a given language. English is
14 | * the default language.
15 | */
16 | language.getText = lang => {
17 | // Setup the return object.
18 | let langData = {};
19 |
20 | // Return data for the appropriate language.
21 | switch (lang) {
22 | case "es":
23 | langData = require("../../data/language/es");
24 | break;
25 | default:
26 | langData = require("../../data/language/en");
27 | }
28 |
29 | // Return the language data for the selected language.
30 | return langData;
31 | }
32 |
33 | // Export the language module.
34 | module.exports = language;
--------------------------------------------------------------------------------
/server/src/util/validForm/index.js:
--------------------------------------------------------------------------------
1 | // Import modules
2 | const validForm = require("./validForm");
3 |
4 | // Export modules
5 | module.exports = validForm;
--------------------------------------------------------------------------------