diff --git a/backend/chainetv/api.py b/backend/chainetv/api.py
index 147fd72..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 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/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/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 @@
+
+
+
+
+
+
+
Login
+
{{ errorMsg }}
+
+
+
+
+
+
+
+
+
\ 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"}'`