diff --git a/mpwo_api/mpwo_api/__init__.py b/mpwo_api/mpwo_api/__init__.py index 7cb2f79c..e4b85613 100644 --- a/mpwo_api/mpwo_api/__init__.py +++ b/mpwo_api/mpwo_api/__init__.py @@ -32,3 +32,16 @@ if app.debug: ).handlers = logging.getLogger('werkzeug').handlers logging.getLogger('sqlalchemy.orm').setLevel(logging.WARNING) appLog.setLevel(logging.DEBUG) + +if app.debug : + # Enable CORS + @app.after_request + def after_request(response): + response.headers.add('Access-Control-Allow-Origin', '*') + response.headers.add( + 'Access-Control-Allow-Headers', 'Content-Type,Authorization' + ) + response.headers.add( + 'Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH,OPTIONS' + ) + return response diff --git a/mpwo_api/mpwo_api/tests/test_auth.py b/mpwo_api/mpwo_api/tests/test_auth.py index d6c0b097..027a06b3 100644 --- a/mpwo_api/mpwo_api/tests/test_auth.py +++ b/mpwo_api/mpwo_api/tests/test_auth.py @@ -191,3 +191,42 @@ class TestAuthBlueprint(BaseTestCase): self.assertTrue( data['message'] == 'Invalid token. Please log in again.') self.assertEqual(response.status_code, 401) + + def test_user_profile(self): + add_user('test', 'test@test.com', 'test') + with self.client: + resp_login = self.client.post( + '/api/auth/login', + data=json.dumps(dict( + email='test@test.com', + password='test' + )), + content_type='application/json' + ) + response = self.client.get( + '/api/auth/profile', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + self.assertTrue(data['status'] == 'success') + self.assertTrue(data['data'] is not None) + self.assertTrue(data['data']['username'] == 'test') + self.assertTrue(data['data']['email'] == 'test@test.com') + self.assertTrue(data['data']['created_at']) + self.assertFalse(data['data']['admin']) + self.assertEqual(response.status_code, 200) + + def test_invalid_profile(self): + with self.client: + response = self.client.get( + '/api/auth/profile', + headers=dict(Authorization='Bearer invalid')) + data = json.loads(response.data.decode()) + self.assertTrue(data['status'] == 'error') + self.assertTrue( + data['message'] == 'Invalid token. Please log in again.') + self.assertEqual(response.status_code, 401) diff --git a/mpwo_api/mpwo_api/users/auth.py b/mpwo_api/mpwo_api/users/auth.py index bf69d588..d05e14ed 100644 --- a/mpwo_api/mpwo_api/users/auth.py +++ b/mpwo_api/mpwo_api/users/auth.py @@ -4,6 +4,7 @@ from sqlalchemy import exc, or_ from mpwo_api import appLog, bcrypt, db from .models import User +from .utils import authenticate auth_blueprint = Blueprint('auth', __name__) @@ -12,7 +13,9 @@ auth_blueprint = Blueprint('auth', __name__) def register_user(): # get post data post_data = request.get_json() - if not post_data: + if not post_data or post_data.get('username') is None \ + or post_data.get('email') is None \ + or post_data.get('password') is None: response_object = { 'status': 'error', 'message': 'Invalid payload.' @@ -52,9 +55,10 @@ def register_user(): except (exc.IntegrityError, exc.OperationalError, ValueError) as e: db.session.rollback() appLog.error(e) + response_object = { 'status': 'error', - 'message': 'Invalid payload.' + 'message': 'Error. Please try again or contact the administrator.' } return jsonify(response_object), 400 @@ -95,19 +99,20 @@ def login_user(): appLog.error(e) response_object = { 'status': 'error', - 'message': 'Try again' + 'message': 'Error. Please try again or contact the administrator.' } return jsonify(response_object), 500 @auth_blueprint.route('/auth/logout', methods=['GET']) -def logout_user(): +@authenticate +def logout_user(user_id): # get auth token auth_header = request.headers.get('Authorization') if auth_header: auth_token = auth_header.split(" ")[1] resp = User.decode_auth_token(auth_token) - if not isinstance(resp, str): + if not isinstance(user_id, str): response_object = { 'status': 'success', 'message': 'Successfully logged out.' @@ -125,3 +130,20 @@ def logout_user(): 'message': 'Provide a valid auth token.' } return jsonify(response_object), 403 + + +@auth_blueprint.route('/auth/profile', methods=['GET']) +@authenticate +def get_user_status(user_id): + user = User.query.filter_by(id=user_id).first() + response_object = { + 'status': 'success', + 'data': { + 'id': user.id, + 'username': user.username, + 'email': user.email, + 'created_at': user.created_at, + 'admin': user.admin, + } + } + return jsonify(response_object), 200 diff --git a/mpwo_api/mpwo_api/users/utils.py b/mpwo_api/mpwo_api/users/utils.py new file mode 100644 index 00000000..0e652ef5 --- /dev/null +++ b/mpwo_api/mpwo_api/users/utils.py @@ -0,0 +1,35 @@ +from functools import wraps + +from flask import request, jsonify + +from .models import User + + +def authenticate(f): + @wraps(f) + def decorated_function(*args, **kwargs): + response_object = { + 'status': 'error', + 'message': 'Something went wrong. Please contact us.' + } + code = 401 + auth_header = request.headers.get('Authorization') + if not auth_header: + response_object['message'] = 'Provide a valid auth token.' + code = 403 + return jsonify(response_object), code + auth_token = auth_header.split(" ")[1] + resp = User.decode_auth_token(auth_token) + if isinstance(resp, str): + response_object['message'] = resp + return jsonify(response_object), code + user = User.query.filter_by(id=resp).first() + if not user: + return jsonify(response_object), code + return f(resp, *args, **kwargs) + return decorated_function + + +def is_admin(user_id): + user = User.query.filter_by(id=user_id).first() + return user.admin diff --git a/mpwo_client/src/actions/index.js b/mpwo_client/src/actions/index.js index e69de29b..2a80528e 100644 --- a/mpwo_client/src/actions/index.js +++ b/mpwo_client/src/actions/index.js @@ -0,0 +1,117 @@ +import mpwoApi from '../mpwoApi' + +function AuthError(message) { + return { type: 'AUTH_ERROR', message } +} + +function ProfileSuccess(message) { + return { type: 'PROFILE_SUCCESS', message } +} + +function ProfileError(message) { + return { type: 'PROFILE_ERROR', message } +} + +const updateFormDataEmail = value => ({ + type: 'UPDATE_FORMDATA_EMAIL', + email: value, +}) + +const updateFormDataUsername = value => ({ + type: 'UPDATE_FORMDATA_USERNAME', + username: value, +}) + +const updateFormDataPassword = value => ({ + type: 'UPDATE_FORMDATA_PASSWORD', + password: value, +}) + +export function getProfile(dispatch) { + return mpwoApi + .getProfile() + .then(ret => { + if (ret.status === 'success') { + dispatch(ProfileSuccess(ret)) + } else { + dispatch(ProfileError(ret.message)) + } + }) + .catch(error => { + throw error + }) +} + +export function register(formData) { + return function(dispatch) { + return mpwoApi + .register(formData.username, formData.email, formData.password) + .then(ret => { + if (ret.status === 'success') { + window.localStorage.setItem('authToken', ret.auth_token) + getProfile(dispatch) + } else { + dispatch(AuthError(ret.message)) + } + }) + .catch(error => { + throw error + }) + } +} + +export function login(formData) { + return function(dispatch) { + return mpwoApi + .login(formData.email, formData.password) + .then(ret => { + if (ret.status === 'success') { + window.localStorage.setItem('authToken', ret.auth_token) + getProfile(dispatch) + } else { + dispatch(AuthError(ret.message)) + } + }) + .catch(error => { + throw error + }) + } +} + +export function loadProfile() { + if (window.localStorage.getItem('authToken')) { + return function(dispatch) { + getProfile(dispatch) + } + } + return { type: 'LOGOUT' } +} + +export function logout() { + return { type: 'LOGOUT' } +} + +export function handleUserFormSubmit(event, formType) { + event.preventDefault() + return (dispatch, getState) => { + const state = getState() + let { formData } = state.formData + formData.formData = state.formData.formData + if (formType === 'Login') { + dispatch(login(formData.formData)) + } else { // formType === 'Register' + dispatch(register(formData.formData)) + } + } +} + +export const handleFormChange = event => dispatch => { + switch (event.target.name) { + case 'email': + return dispatch(updateFormDataEmail(event.target.value)) + case 'username': + return dispatch(updateFormDataUsername(event.target.value)) + default: // case 'password': + return dispatch(updateFormDataPassword(event.target.value)) + } +} diff --git a/mpwo_client/src/components/App.jsx b/mpwo_client/src/components/App.jsx index d1ad0291..4b9ff909 100644 --- a/mpwo_client/src/components/App.jsx +++ b/mpwo_client/src/components/App.jsx @@ -1,7 +1,10 @@ import React from 'react' +import { Route, Switch } from 'react-router-dom' import './App.css' +import Logout from './Logout' import NavBar from './NavBar' +import UserForm from './User/UserForm' export default class App extends React.Component { constructor(props) { @@ -13,9 +16,37 @@ export default class App extends React.Component { return (
- App in progress -
++ You are now logged out. + Click here to log back in.
+{props.message}
+ )}
+