Skip to content

Commit c863c77

Browse files
authored
Merge pull request #262 from CodeForPhilly/261-JWTs
Requires JWT for all API calls
2 parents 98a1ea2 + 7d60548 commit c863c77

File tree

9 files changed

+69
-15
lines changed

9 files changed

+69
-15
lines changed

src/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ Brutally clean all docker related items from your machine
4848
--------------------------
4949
Troubleshooting
5050
---------------------------------------
51-
See the [Troubleshooting page](https://github.com/CodeForPhilly/paws-data-pipeline/wiki/Troubleshooting) at the GitHub wiki.
51+
See the [Troubleshooting page](https://github.com/CodeForPhilly/paws-data-pipeline/wiki/Troubleshooting) at the GitHub wiki.

src/client/src/App.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Refresh from './components/Refresh';
1717
import useToken from './components/Login/useToken';
1818
var jwt = require('jsonwebtoken');
1919

20+
const REFRESH_POPUP_TIME = 300 // seconds
2021

2122
// Triggers token expiration check
2223
const sleep = time => new Promise(resolve => setTimeout(resolve, time))
@@ -80,7 +81,7 @@ function AuthenticatedApp() {
8081
var expTime = decoded?.payload.exp - Date.now()/1000;
8182
const jwtExpired = expTime <= 0
8283

83-
const popRefreshAlert = expTime > 0 && expTime < 30; // Time in secs to pop up refresh dialog
84+
const popRefreshAlert = expTime > 0 && expTime < REFRESH_POPUP_TIME; // Time in secs to pop up refresh dialog
8485

8586
const hdr = userRole === 'admin' ? <AdminHeader /> : <Header /> // If we're going to display a header, which one?
8687

@@ -102,14 +103,14 @@ function AuthenticatedApp() {
102103
(!access_token | jwtExpired) ? <Login setToken={setToken} /> : <Switch>
103104

104105
<Route exact path="/">
105-
<HomePage/>
106+
<HomePage access_token = {access_token}/>
106107
</Route>
107108

108109

109110
{ /* If an admin, render Upload page */
110111
userRole === 'admin' &&
111112
<Route path="/admin">
112-
<Admin/>
113+
<Admin access_token = {access_token}/>
113114
</Route>
114115
}
115116

@@ -119,11 +120,11 @@ function AuthenticatedApp() {
119120
</Route>
120121

121122
<Route path="/360view/search">
122-
<Search360/>
123+
<Search360 access_token = {access_token} />
123124
</Route>
124125

125126
<Route path="/360view/view">
126-
<View360/>
127+
<View360 access_token = {access_token} />
127128
</Route>
128129

129130
<Route path="/check">

src/client/src/components/RefreshDlg.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default function RefreshDlg(props) {
1717
const handleClose = async (shouldRefresh) => {
1818
// Could be closed with Yes, No, outclick (which equals No)
1919
setOpen(false);
20-
if (props.shouldOpen){
20+
if (shouldRefresh){
2121
const new_at = await Refresh(access_token);
2222
props.setToken(new_at);
2323
}

src/client/src/pages/Admin.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ class Admin extends Component {
7878
formData.append('file', element, element.name)
7979
})
8080

81-
await fetch("/api/file", {method: 'POST', body: formData})
81+
await fetch("/api/file", {method: 'POST', body: formData,
82+
headers: {
83+
'Content-Type': 'application/json',
84+
'Authorization': 'Bearer ' + this.props.access_token
85+
}})
86+
87+
8288

8389
await this.handleGetFileList();
8490

@@ -95,13 +101,27 @@ class Admin extends Component {
95101

96102
this.setState({isLoading: true});
97103

98-
await fetch('/api/execute');
104+
await fetch('/api/execute',
105+
{
106+
method: 'GET',
107+
headers: {
108+
'Content-Type': 'application/json',
109+
'Authorization': 'Bearer ' + this.props.access_token
110+
}
111+
});
99112

100113
this.refreshPage();
101114
}
102115

103116
async handleGetStatistics() {
104-
const statsData = await fetch("/api/statistics");
117+
const statsData = await fetch("/api/statistics",
118+
{
119+
method: 'GET',
120+
headers: {
121+
'Content-Type': 'application/json',
122+
'Authorization': 'Bearer ' + this.props.access_token
123+
}
124+
});
105125
const statsResponse = await statsData.json()
106126

107127
if (statsResponse !== 'executing') {
@@ -115,7 +135,14 @@ class Admin extends Component {
115135
}
116136

117137
async handleGetFileList() {
118-
const filesData = await fetch("/api/listCurrentFiles");
138+
const filesData = await fetch("/api/listCurrentFiles",
139+
{
140+
method: 'GET',
141+
headers: {
142+
'Content-Type': 'application/json',
143+
'Authorization': 'Bearer ' + this.props.access_token
144+
}
145+
});
119146
const filesResponse = await filesData.json();
120147
this.setState({fileListHtml: filesResponse});
121148
}

src/client/src/pages/DataView360/Search/Search.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,15 @@ class Search360 extends Component {
170170
this.setState({isDataBusy: true, search_participant: search_participant});
171171

172172
await new Promise(resolve => setTimeout(resolve, 1000));
173-
let response = await fetch(`/api/contacts/${search_participant}`);
173+
let response = await fetch(`/api/contacts/${search_participant}`,
174+
{
175+
method: 'GET',
176+
headers: {
177+
'Content-Type': 'application/json',
178+
'Authorization': 'Bearer ' + this.props.access_token
179+
}
180+
}
181+
);
174182
response = await response.json();
175183

176184
await this.setState({participantList: response.result})

src/client/src/pages/DataView360/View/View.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@ class View360 extends Component {
5454
});
5555

5656
await new Promise(resolve => setTimeout(resolve, 1000));
57-
let response = await fetch(`/api/360/${this.state.matchId}`);
57+
let response = await fetch(`/api/360/${this.state.matchId}`,
58+
{
59+
method: 'GET',
60+
headers: {
61+
'Content-Type': 'application/json',
62+
'Authorization': 'Bearer ' + this.props.access_token
63+
}
64+
} );
5865
response = await response.json();
5966

6067
this.setState({

src/server/api/admin_api.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from config import engine
1212
from flask import request, redirect, jsonify, current_app, abort
1313
from api.file_uploader import validate_and_arrange_upload
14+
15+
from api import jwt_ops
1416
from config import (
1517
RAW_DATA_PATH,
1618
CURRENT_SOURCE_FILES_PATH,
@@ -26,6 +28,7 @@ def __allowed_file(filename):
2628

2729
# file upload tutorial
2830
@admin_api.route("/api/file", methods=["POST"])
31+
@jwt_ops.admin_required
2932
def uploadCSV():
3033
if "file" not in request.files:
3134
return redirect(request.url)
@@ -43,6 +46,7 @@ def uploadCSV():
4346

4447

4548
@admin_api.route("/api/listCurrentFiles", methods=["GET"])
49+
@jwt_ops.admin_required
4650
def list_current_files():
4751
result = None
4852

@@ -56,6 +60,7 @@ def list_current_files():
5660

5761

5862
@admin_api.route("/api/execute", methods=["GET"])
63+
@jwt_ops.admin_required
5964
def execute():
6065
current_app.logger.info("Execute flow")
6166
flow_script.start_flow()
@@ -109,6 +114,7 @@ def get_statistics():
109114

110115

111116
@admin_api.route("/api/statistics", methods=["GET"])
117+
@jwt_ops.admin_required
112118
def list_statistics():
113119
""" Pull Last Execution stats from DB. """
114120
current_app.logger.info("list_statistics() request")
@@ -134,6 +140,7 @@ def list_statistics():
134140

135141

136142
@admin_api.route("/api/get_execution_status/<int:job_id>", methods=["GET"])
143+
@jwt_ops.admin_required
137144
def get_exec_status(job_id):
138145
""" Get the execution status record from the DB for the specified job_id """
139146

src/server/api/common_api.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import dateutil.parser
1010
from secrets import SHELTERLUV_SECRET_TOKEN
1111

12+
from api import jwt_ops
13+
1214

1315
@common_api.route('/api/timeout_test/<duration>', methods=['GET'])
1416
def get_timeout(duration):
@@ -21,6 +23,7 @@ def get_timeout(duration):
2123
return results
2224

2325
@common_api.route('/api/contacts/<search_text>', methods=['GET'])
26+
@jwt_ops.jwt_required()
2427
def get_contacts(search_text):
2528
with engine.connect() as connection:
2629
search_text = search_text.lower()
@@ -45,6 +48,7 @@ def get_contacts(search_text):
4548

4649

4750
@common_api.route('/api/360/<matching_id>', methods=['GET'])
51+
@jwt_ops.jwt_required()
4852
def get_360(matching_id):
4953
result = {}
5054

src/server/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010

1111

1212
app.config["JWT_SECRET_KEY"] = JWT_SECRET
13-
app.config["JWT_MAX_TIMEOUT"] = 7200 #Seconds
13+
app.config["JWT_MAX_TIMEOUT"] = 30*60 #Seconds
1414

1515
# We'll use max for default but can be reduced for testing
16-
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = 60 # app.config["JWT_MAX_TIMEOUT"]
16+
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = app.config["JWT_MAX_TIMEOUT"]
1717

1818
jwt = JWTManager(app)
1919

0 commit comments

Comments
 (0)