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 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') })
|
||||
|
||||
|
||||
|
@ -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('/<path:path>')
|
||||
|
@ -1,3 +1,3 @@
|
||||
class BaseConfig(object):
|
||||
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
|
||||
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
|
||||
|
@ -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;
|
||||
}
|
||||
</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() {
|
||||
|
||||
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')
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
h1,
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
<style >
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.is-success {
|
||||
background-color: #4caf50;
|
||||
}
|
||||
|
||||
.is-hoverable {
|
||||
color: #42b983;
|
||||
}
|
||||
</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 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()
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -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({
|
||||
|
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