Merge branch 'dev' of vincent/chainetv_web into master
This commit is contained in:
commit
75699aa733
@ -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 chainetv.Jsonfile import JSONfile
|
from .Jsonfile import JSONfile
|
||||||
from chainetv import emission
|
from . import emission
|
||||||
|
import jwt
|
||||||
|
from functools import wraps
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from .user import User
|
||||||
data= JSONfile("chaine.json")
|
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 = Blueprint("api", __name__)
|
||||||
@api.route('/ping', methods=['GET'])
|
@api.route('/ping', methods=['GET'])
|
||||||
@ -18,7 +54,8 @@ def get_chaine(num):
|
|||||||
return jsonify(chaine)
|
return jsonify(chaine)
|
||||||
|
|
||||||
@api.route('/chaine/', methods=['put'])
|
@api.route('/chaine/', methods=['put'])
|
||||||
def update_list():
|
@token_required
|
||||||
|
def update_list(user):
|
||||||
status=data.parsechaine()
|
status=data.parsechaine()
|
||||||
if(status=='ok'):
|
if(status=='ok'):
|
||||||
return jsonify("OK")
|
return jsonify("OK")
|
||||||
@ -32,3 +69,28 @@ def get_emmission(num):
|
|||||||
return make_response("",204)
|
return make_response("",204)
|
||||||
else:
|
else:
|
||||||
return jsonify(emission.parse_emmission(chaine))
|
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') })
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ def create_app(app_name=__name__):
|
|||||||
app.config.from_object('chainetv.config.BaseConfig')
|
app.config.from_object('chainetv.config.BaseConfig')
|
||||||
#app.wsgi_app = ReverseProxied(app.wsgi_app)
|
#app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
from chainetv.api import api
|
from .api import api
|
||||||
app.register_blueprint(api, url_prefix="/api/v1")
|
app.register_blueprint(api, url_prefix="/api/v1")
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@app.route('/<path:path>')
|
@app.route('/<path:path>')
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
class BaseConfig(object):
|
class BaseConfig(object):
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
SECRET_KEY = 'mysecretkeyg'
|
SECRET_KEY = '53FEFEGEGEGFTGETRF354353'
|
28
backend/chainetv/user.py
Normal file
28
backend/chainetv/user.py
Normal file
@ -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)
|
@ -4,12 +4,14 @@ bs4==0.0.1
|
|||||||
Click==7.0
|
Click==7.0
|
||||||
Flask==1.0.2
|
Flask==1.0.2
|
||||||
Flask-Cors==3.0.7
|
Flask-Cors==3.0.7
|
||||||
|
gunicorn==19.9.0
|
||||||
isort==4.3.17
|
isort==4.3.17
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
Jinja2==2.10.1
|
Jinja2==2.10.1
|
||||||
lazy-object-proxy==1.3.1
|
lazy-object-proxy==1.3.1
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
mccabe==0.6.1
|
mccabe==0.6.1
|
||||||
|
PyJWT==1.7.1
|
||||||
pylint==2.3.1
|
pylint==2.3.1
|
||||||
six==1.12.0
|
six==1.12.0
|
||||||
soupsieve==1.9.1
|
soupsieve==1.9.1
|
||||||
|
@ -18,4 +18,22 @@ export default {
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -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}` }});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function authenticate (userData) {
|
||||||
|
return axios.post(`${API_PATH}/login/`, userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function register (userData) {
|
||||||
|
// return axios.post(`${API_URL}/register/`, userData)
|
||||||
|
// }
|
@ -100,39 +100,33 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
parsechaine() {
|
parsechaine() {
|
||||||
|
if (!this.$store.getters.isAuthenticated) {
|
||||||
return putparsechaine()
|
this.$router.push('/login')
|
||||||
|
} else {
|
||||||
|
console.log(this.$store.state.jwt.token)
|
||||||
|
return putparsechaine(this.$store.state.jwt.token)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.data == "OK") {
|
if (res.data == "OK") {
|
||||||
alert("update database OK");
|
alert("update database OK");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(res => {
|
.catch(error => {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
alert("error during database update");
|
if (error.response) {
|
||||||
console.error(res);
|
alert(error.response.data.message);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
this.$router.push('/login')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style >
|
||||||
h1,
|
|
||||||
h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-success {
|
|
||||||
background-color: #4caf50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-hoverable {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
76
client/src/components/login.vue
Normal file
76
client/src/components/login.vue
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!-- components/Login.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-success">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">Login </h2>
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large" for="name">Name:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="name" class="input is-large" id="email" v-model="name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large" for="password">Password:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="password" class="input is-large" id="password" v-model="password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-large is-success" @click="authenticate">Login</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from "../utils"
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
password: '',
|
||||||
|
errorMsg: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
authenticate () {
|
||||||
|
this.$store.dispatch('login', { name: this.name, password: this.password })
|
||||||
|
.then(() =>{
|
||||||
|
if (this.$store.getters.isAuthenticated){
|
||||||
|
this.$router.push('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
},
|
||||||
|
register () {
|
||||||
|
this.$store.dispatch('register', { name: this.name, password: this.password })
|
||||||
|
.then(() => this.$router.push('/'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
EventBus.$on('failedRegistering', (msg) => {
|
||||||
|
this.errorMsg = msg
|
||||||
|
})
|
||||||
|
EventBus.$on('failedAuthentication', (msg) => {
|
||||||
|
this.errorMsg = msg
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
EventBus.$off('failedRegistering')
|
||||||
|
EventBus.$off('failedAuthentication')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -2,7 +2,8 @@ import Vue from 'vue';
|
|||||||
import Router from 'vue-router';
|
import Router from 'vue-router';
|
||||||
import ping from '@/components/ping';
|
import ping from '@/components/ping';
|
||||||
import chainetv from '@/components/chainetv';
|
import chainetv from '@/components/chainetv';
|
||||||
|
import login from '@/components/login';
|
||||||
|
import store from '@/store'
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
export default new Router({
|
export default new Router({
|
||||||
@ -12,10 +13,22 @@ export default new Router({
|
|||||||
name: 'chaineTV',
|
name: 'chaineTV',
|
||||||
component: chainetv,
|
component: chainetv,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
component: login
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/ping',
|
path: '/ping',
|
||||||
name: 'ping',
|
name: 'ping',
|
||||||
component: ping,
|
component: ping,
|
||||||
|
beforeEnter (to, from, next) {
|
||||||
|
if (!store.getters.isAuthenticated) {
|
||||||
|
next('/login')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import { fetchchaine, fetchemission } from '../api';
|
import { fetchchaine, fetchemission,authenticate } from '../api';
|
||||||
|
import { isValidJwt, EventBus } from '../utils'
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
const state = {
|
const state = {
|
||||||
// single source of data
|
// single source of data
|
||||||
arrayresultchaines: [],
|
arrayresultchaines: [],
|
||||||
|
user: {},
|
||||||
|
jwt: ''
|
||||||
}
|
}
|
||||||
const API_PATH=`${process.env.ROOT_API}/api/v1`;
|
const API_PATH=`${process.env.ROOT_API}/api/v1`;
|
||||||
const actions = {
|
const actions = {
|
||||||
@ -32,9 +35,31 @@ const actions = {
|
|||||||
context.commit('push_chaine',{chaine:chaine,name:name,emission:res_emission.data})
|
context.commit('push_chaine',{chaine:chaine,name:name,emission:res_emission.data})
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
context.commit('push_chaine',{chaine:chaine,name:name})
|
||||||
console.error(error);
|
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{
|
}else{
|
||||||
state.arrayresultchaines.push({chaine: payload.chaine,name: payload.name,emission: payload.emission});
|
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 = {
|
const getters = {
|
||||||
// reusable data accessors
|
// reusable data accessors
|
||||||
|
isAuthenticated (state) {
|
||||||
|
return isValidJwt(state.jwt.token)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
|
13
client/src/utils/index.js
Normal file
13
client/src/utils/index.js
Normal file
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user