diff --git a/backend/chainetv/api.py b/backend/chainetv/api.py index 8383852..4253d98 100644 --- a/backend/chainetv/api.py +++ b/backend/chainetv/api.py @@ -1,8 +1,44 @@ -from flask import Blueprint, jsonify, request,make_response,redirect,url_for,render_template -from chainetv.Jsonfile import JSONfile -from chainetv import emission +from flask import Blueprint, jsonify, request,make_response,redirect,url_for,render_template,current_app +from .Jsonfile import JSONfile +from . import emission +import jwt +from functools import wraps +from datetime import datetime, timedelta +from .user import User data= JSONfile("chaine.json") +def token_required(f): + @wraps(f) + def _verify(*args, **kwargs): + auth_headers = request.headers.get('Authorization', '').split() + invalid_msg = { + 'message': 'Invalid token. Registeration and / or authentication required', + 'authenticated': False + } + expired_msg = { + 'message': 'Expired token. Reauthentication required.', + 'authenticated': False + } + + if len(auth_headers) != 2: + return jsonify(invalid_msg), 401 + + try: + token = auth_headers[1] + data = jwt.decode(token,current_app.config['SECRET_KEY']) + user = User + if not user: + raise RuntimeError('User not found') + + return f(user, *args, **kwargs) + except jwt.ExpiredSignatureError: + return jsonify(expired_msg), 401 # 401 is Unauthorized HTTP status code + except (jwt.InvalidTokenError) as e: + print(e) + return jsonify(invalid_msg), 401 + + return _verify + api = Blueprint("api", __name__) @api.route('/ping', methods=['GET']) @@ -18,7 +54,8 @@ def get_chaine(num): return jsonify(chaine) @api.route('/chaine/', methods=['put']) -def update_list(): +@token_required +def update_list(user): status=data.parsechaine() if(status=='ok'): return jsonify("OK") @@ -32,3 +69,28 @@ def get_emmission(num): return make_response("",204) else: return jsonify(emission.parse_emmission(chaine)) + +#@api.route('/register/', methods=('POST',)) +#def register(): +# data = request.get_json() +# user = User(**data) +# db.session.add(user) +# db.session.commit() +# return jsonify(user.to_dict()), 201 + +@api.route('/login/', methods=('POST',)) +def login(): + data = request.get_json() + user = User.authenticate(**data) + + if not user: + return jsonify({ 'message': 'Invalid credentials', 'authenticated': False }), 401 + + token = jwt.encode({ + 'sub': user.name, + 'iat':datetime.utcnow(), + 'exp': datetime.utcnow() + timedelta(minutes=30)}, + current_app.config['SECRET_KEY']) + return jsonify({ 'token': token.decode('UTF-8') }) + + diff --git a/backend/chainetv/app.py b/backend/chainetv/app.py index f4a46e6..83a209e 100644 --- a/backend/chainetv/app.py +++ b/backend/chainetv/app.py @@ -7,7 +7,7 @@ def create_app(app_name=__name__): app.config.from_object('chainetv.config.BaseConfig') #app.wsgi_app = ReverseProxied(app.wsgi_app) CORS(app) - from chainetv.api import api + from .api import api app.register_blueprint(api, url_prefix="/api/v1") @app.route('/') @app.route('/') diff --git a/backend/chainetv/config.py b/backend/chainetv/config.py index 24dea4c..09e3ba1 100644 --- a/backend/chainetv/config.py +++ b/backend/chainetv/config.py @@ -1,3 +1,3 @@ class BaseConfig(object): DEBUG = True - SECRET_KEY = 'mysecretkeyg' \ No newline at end of file + SECRET_KEY = '53FEFEGEGEGFTGETRF354353' \ No newline at end of file diff --git a/backend/chainetv/user.py b/backend/chainetv/user.py new file mode 100644 index 0000000..9f52275 --- /dev/null +++ b/backend/chainetv/user.py @@ -0,0 +1,28 @@ + +from werkzeug.security import generate_password_hash, check_password_hash + +class User(object): + name="vincent" + password=generate_password_hash("test", method='sha256') + id=1 + + # def __init__(self, name, password): + # self.name = name + # self.password = generate_password_hash(password, method='sha256') + + @classmethod + def authenticate(cls, **kwargs): + name = kwargs.get('name') + password = kwargs.get('password') + + if not name or not password: + return None + + user = cls + if not user or not check_password_hash(user.password, password): + return None + + return user + + def to_dict(self): + return dict(id=self.id, name=self.name) \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index 01c76b8..598416c 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,12 +4,14 @@ bs4==0.0.1 Click==7.0 Flask==1.0.2 Flask-Cors==3.0.7 +gunicorn==19.9.0 isort==4.3.17 itsdangerous==1.1.0 Jinja2==2.10.1 lazy-object-proxy==1.3.1 MarkupSafe==1.1.1 mccabe==0.6.1 +PyJWT==1.7.1 pylint==2.3.1 six==1.12.0 soupsieve==1.9.1 diff --git a/client/src/App.vue b/client/src/App.vue index 50e2b61..b670f82 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -18,4 +18,22 @@ export default { -moz-osx-font-smoothing: grayscale; } +h1, +h2 { + font-weight: normal; + font-size: 2em; + font-weight: bold; +} + +p { + text-align: justify; +} + +.is-success { + background-color: #4caf50; +} + +.is-hoverable { + color: #42b983; +} diff --git a/client/src/api/index.js b/client/src/api/index.js index 1793707..cd4d848 100644 --- a/client/src/api/index.js +++ b/client/src/api/index.js @@ -13,8 +13,16 @@ export function fetchemission(num){ } -export function putparsechaine(){ +export function putparsechaine(jwt){ - return axios.put(`${API_PATH}/chaine/`); + return axios.put(`${API_PATH}/chaine/`,'',{ headers: { Authorization: `Bearer: ${jwt}` }}); -} \ No newline at end of file +} + +export function authenticate (userData) { + return axios.post(`${API_PATH}/login/`, userData) +} + +// export function register (userData) { +// return axios.post(`${API_URL}/register/`, userData) +// } \ No newline at end of file diff --git a/client/src/components/chainetv.vue b/client/src/components/chainetv.vue index 72c2b57..7fc1823 100644 --- a/client/src/components/chainetv.vue +++ b/client/src/components/chainetv.vue @@ -100,39 +100,33 @@ export default { }); }, parsechaine() { - - return putparsechaine() - .then(res => { - if (res.data == "OK") { - alert("update database OK"); - } - }) - .catch(res => { - // eslint-disable-next-line - alert("error during database update"); - console.error(res); - }); + if (!this.$store.getters.isAuthenticated) { + this.$router.push('/login') + } else { + console.log(this.$store.state.jwt.token) + return putparsechaine(this.$store.state.jwt.token) + .then(res => { + if (res.data == "OK") { + alert("update database OK"); + } + }) + .catch(error => { + // eslint-disable-next-line + if (error.response) { + alert(error.response.data.message); + + }else{ + + console.error(error); + } + this.$router.push('/login') + }); + } + } } }; - diff --git a/client/src/components/login.vue b/client/src/components/login.vue new file mode 100644 index 0000000..3e7bc0d --- /dev/null +++ b/client/src/components/login.vue @@ -0,0 +1,76 @@ + + + + \ No newline at end of file diff --git a/client/src/router/index.js b/client/src/router/index.js index 0bc4116..6934b0e 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -2,7 +2,8 @@ import Vue from 'vue'; import Router from 'vue-router'; import ping from '@/components/ping'; import chainetv from '@/components/chainetv'; - +import login from '@/components/login'; +import store from '@/store' Vue.use(Router); export default new Router({ @@ -12,10 +13,22 @@ export default new Router({ name: 'chaineTV', component: chainetv, }, + { + path: '/login', + name: 'Login', + component: login + }, { path: '/ping', name: 'ping', component: ping, + beforeEnter (to, from, next) { + if (!store.getters.isAuthenticated) { + next('/login') + } else { + next() + } + } }, ], }); diff --git a/client/src/store/index.js b/client/src/store/index.js index 1b8c855..69a9202 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.js @@ -2,11 +2,14 @@ import Vue from 'vue' import Vuex from 'vuex' -import { fetchchaine, fetchemission } from '../api'; +import { fetchchaine, fetchemission,authenticate } from '../api'; +import { isValidJwt, EventBus } from '../utils' Vue.use(Vuex) const state = { // single source of data arrayresultchaines: [], + user: {}, + jwt: '' } const API_PATH=`${process.env.ROOT_API}/api/v1`; const actions = { @@ -32,9 +35,31 @@ const actions = { context.commit('push_chaine',{chaine:chaine,name:name,emission:res_emission.data}) }) .catch(error => { + context.commit('push_chaine',{chaine:chaine,name:name}) console.error(error); }); }, + login (context, userData) { + context.commit('setUserData', { userData }) + return authenticate(userData) + .then(response => context.commit('setJwtToken', { jwt: response.data })) + .catch(error => { + + console.log('Error Authenticating: ', error) + EventBus.$emit('failedAuthentication', error.response.data.message) + + + }) + }, +// register (context, userData) { +// context.commit('setUserData', { userData }) +// return register(userData) +// .then(context.dispatch('login', userData)) +// .catch(error => { +// console.log('Error Registering: ', error) +// EventBus.$emit('failedRegistering: ', error) +// }) +// } } @@ -46,11 +71,23 @@ const mutations = { }else{ state.arrayresultchaines.push({chaine: payload.chaine,name: payload.name,emission: payload.emission}); } + }, + setUserData (state, payload) { + console.log('setUserData payload = ', payload) + state.userData = payload.userData + }, + setJwtToken (state, payload) { + console.log('setJwtToken payload = ', payload) + localStorage.token = payload.jwt.token + state.jwt = payload.jwt } } const getters = { // reusable data accessors + isAuthenticated (state) { + return isValidJwt(state.jwt.token) + } } const store = new Vuex.Store({ diff --git a/client/src/utils/index.js b/client/src/utils/index.js new file mode 100644 index 0000000..476cf61 --- /dev/null +++ b/client/src/utils/index.js @@ -0,0 +1,13 @@ +import Vue from 'vue' + +export const EventBus = new Vue() + +export function isValidJwt (jwt) { + if (!jwt || jwt.split('.').length < 3) { + return false + } + const data = JSON.parse(atob(jwt.split('.')[1])) + const exp = new Date(data.exp * 1000) // JS deals with dates in milliseconds since epoch + const now = new Date() + return now < exp +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ad3df23 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +#chaine_tv_web + +- test request authent:`curl -X POST -H 'Content-Type: application/json' -i 'http://127.0.0.1:5000/api/v1/login/' --data '{"email":"vincent","password":"test"}'`