Feature/use jwt in web (#533)
Co-authored-by: crazywoola <li.zheng@dentsplysirona.com> Co-authored-by: StyleZhang <jasonapring2015@outlook.com>pull/545/head
parent
57de19a5ca
commit
d49ac1e4ac
@ -0,0 +1,64 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
import uuid
|
||||||
|
from controllers.web import api
|
||||||
|
from flask_restful import Resource
|
||||||
|
from flask import request
|
||||||
|
from werkzeug.exceptions import Unauthorized, NotFound
|
||||||
|
from models.model import Site, EndUser, App
|
||||||
|
from extensions.ext_database import db
|
||||||
|
from libs.passport import PassportService
|
||||||
|
|
||||||
|
class PassportResource(Resource):
|
||||||
|
"""Base resource for passport."""
|
||||||
|
def get(self):
|
||||||
|
app_id = request.headers.get('X-App-Code')
|
||||||
|
if app_id is None:
|
||||||
|
raise Unauthorized('X-App-Code header is missing.')
|
||||||
|
|
||||||
|
# get site from db and check if it is normal
|
||||||
|
site = db.session.query(Site).filter(
|
||||||
|
Site.code == app_id,
|
||||||
|
Site.status == 'normal'
|
||||||
|
).first()
|
||||||
|
if not site:
|
||||||
|
raise NotFound()
|
||||||
|
# get app from db and check if it is normal and enable_site
|
||||||
|
app_model = db.session.query(App).filter(App.id == site.app_id).first()
|
||||||
|
if not app_model or app_model.status != 'normal' or not app_model.enable_site:
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
|
end_user = EndUser(
|
||||||
|
tenant_id=app_model.tenant_id,
|
||||||
|
app_id=app_model.id,
|
||||||
|
type='browser',
|
||||||
|
is_anonymous=True,
|
||||||
|
session_id=generate_session_id(),
|
||||||
|
)
|
||||||
|
db.session.add(end_user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"iss": site.app_id,
|
||||||
|
'sub': 'Web API Passport',
|
||||||
|
'app_id': site.app_id,
|
||||||
|
'end_user_id': end_user.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
tk = PassportService().issue(payload)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'access_token': tk,
|
||||||
|
}
|
||||||
|
|
||||||
|
api.add_resource(PassportResource, '/passport')
|
||||||
|
|
||||||
|
def generate_session_id():
|
||||||
|
"""
|
||||||
|
Generate a unique session ID.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
session_id = str(uuid.uuid4())
|
||||||
|
existing_count = db.session.query(EndUser) \
|
||||||
|
.filter(EndUser.session_id == session_id).count()
|
||||||
|
if existing_count == 0:
|
||||||
|
return session_id
|
||||||
@ -1,110 +1,48 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
import uuid
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from flask import request, session
|
from flask import request
|
||||||
from flask_restful import Resource
|
from flask_restful import Resource
|
||||||
from werkzeug.exceptions import NotFound, Unauthorized
|
from werkzeug.exceptions import NotFound, Unauthorized
|
||||||
|
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from models.model import App, Site, EndUser
|
from models.model import App, EndUser
|
||||||
|
from libs.passport import PassportService
|
||||||
|
|
||||||
|
def validate_jwt_token(view=None):
|
||||||
def validate_token(view=None):
|
|
||||||
def decorator(view):
|
def decorator(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
site = validate_and_get_site()
|
app_model, end_user = decode_jwt_token()
|
||||||
|
|
||||||
app_model = db.session.query(App).filter(App.id == site.app_id).first()
|
|
||||||
if not app_model:
|
|
||||||
raise NotFound()
|
|
||||||
|
|
||||||
if app_model.status != 'normal':
|
|
||||||
raise NotFound()
|
|
||||||
|
|
||||||
if not app_model.enable_site:
|
|
||||||
raise NotFound()
|
|
||||||
|
|
||||||
end_user = create_or_update_end_user_for_session(app_model)
|
|
||||||
|
|
||||||
return view(app_model, end_user, *args, **kwargs)
|
return view(app_model, end_user, *args, **kwargs)
|
||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
if view:
|
if view:
|
||||||
return decorator(view)
|
return decorator(view)
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
def decode_jwt_token():
|
||||||
def validate_and_get_site():
|
|
||||||
"""
|
|
||||||
Validate and get API token.
|
|
||||||
"""
|
|
||||||
auth_header = request.headers.get('Authorization')
|
auth_header = request.headers.get('Authorization')
|
||||||
if auth_header is None:
|
if auth_header is None:
|
||||||
raise Unauthorized('Authorization header is missing.')
|
raise Unauthorized('Authorization header is missing.')
|
||||||
|
|
||||||
if ' ' not in auth_header:
|
if ' ' not in auth_header:
|
||||||
raise Unauthorized('Invalid Authorization header format. Expected \'Bearer <api-key>\' format.')
|
raise Unauthorized('Invalid Authorization header format. Expected \'Bearer <api-key>\' format.')
|
||||||
|
|
||||||
auth_scheme, auth_token = auth_header.split(None, 1)
|
auth_scheme, tk = auth_header.split(None, 1)
|
||||||
auth_scheme = auth_scheme.lower()
|
auth_scheme = auth_scheme.lower()
|
||||||
|
|
||||||
if auth_scheme != 'bearer':
|
if auth_scheme != 'bearer':
|
||||||
raise Unauthorized('Invalid Authorization header format. Expected \'Bearer <api-key>\' format.')
|
raise Unauthorized('Invalid Authorization header format. Expected \'Bearer <api-key>\' format.')
|
||||||
|
decoded = PassportService().verify(tk)
|
||||||
site = db.session.query(Site).filter(
|
app_model = db.session.query(App).filter(App.id == decoded['app_id']).first()
|
||||||
Site.code == auth_token,
|
if not app_model:
|
||||||
Site.status == 'normal'
|
raise NotFound()
|
||||||
).first()
|
end_user = db.session.query(EndUser).filter(EndUser.id == decoded['end_user_id']).first()
|
||||||
|
if not end_user:
|
||||||
if not site:
|
|
||||||
raise NotFound()
|
raise NotFound()
|
||||||
|
|
||||||
return site
|
return app_model, end_user
|
||||||
|
|
||||||
|
|
||||||
def create_or_update_end_user_for_session(app_model):
|
|
||||||
"""
|
|
||||||
Create or update session terminal based on session ID.
|
|
||||||
"""
|
|
||||||
if 'session_id' not in session:
|
|
||||||
session['session_id'] = generate_session_id()
|
|
||||||
|
|
||||||
session_id = session.get('session_id')
|
|
||||||
end_user = db.session.query(EndUser) \
|
|
||||||
.filter(
|
|
||||||
EndUser.session_id == session_id,
|
|
||||||
EndUser.type == 'browser'
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if end_user is None:
|
|
||||||
end_user = EndUser(
|
|
||||||
tenant_id=app_model.tenant_id,
|
|
||||||
app_id=app_model.id,
|
|
||||||
type='browser',
|
|
||||||
is_anonymous=True,
|
|
||||||
session_id=session_id
|
|
||||||
)
|
|
||||||
db.session.add(end_user)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return end_user
|
|
||||||
|
|
||||||
|
|
||||||
def generate_session_id():
|
|
||||||
"""
|
|
||||||
Generate a unique session ID.
|
|
||||||
"""
|
|
||||||
count = 1
|
|
||||||
session_id = ''
|
|
||||||
while count != 0:
|
|
||||||
session_id = str(uuid.uuid4())
|
|
||||||
count = db.session.query(EndUser) \
|
|
||||||
.filter(EndUser.session_id == session_id).count()
|
|
||||||
|
|
||||||
return session_id
|
|
||||||
|
|
||||||
|
|
||||||
class WebApiResource(Resource):
|
class WebApiResource(Resource):
|
||||||
method_decorators = [validate_token]
|
method_decorators = [validate_jwt_token]
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
import jwt
|
||||||
|
from werkzeug.exceptions import Unauthorized
|
||||||
|
from flask import current_app
|
||||||
|
class PassportService:
|
||||||
|
def __init__(self):
|
||||||
|
self.sk = current_app.config.get('SECRET_KEY')
|
||||||
|
|
||||||
|
def issue(self, payload):
|
||||||
|
return jwt.encode(payload, self.sk, algorithm='HS256')
|
||||||
|
|
||||||
|
def verify(self, token):
|
||||||
|
try:
|
||||||
|
return jwt.decode(token, self.sk, algorithms=['HS256'])
|
||||||
|
except jwt.exceptions.InvalidSignatureError:
|
||||||
|
raise Unauthorized('Invalid token signature.')
|
||||||
|
except jwt.exceptions.DecodeError:
|
||||||
|
raise Unauthorized('Invalid token.')
|
||||||
|
except jwt.exceptions.ExpiredSignatureError:
|
||||||
|
raise Unauthorized('Token has expired.')
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import { fetchAccessToken } from '@/service/share'
|
||||||
|
|
||||||
|
export const checkOrSetAccessToken = async () => {
|
||||||
|
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
|
||||||
|
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
|
||||||
|
let accessTokenJson = { [sharedToken]: '' }
|
||||||
|
try {
|
||||||
|
accessTokenJson = JSON.parse(accessToken)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!accessTokenJson[sharedToken]) {
|
||||||
|
const res = await fetchAccessToken(sharedToken)
|
||||||
|
accessTokenJson[sharedToken] = res.access_token
|
||||||
|
localStorage.setItem('token', JSON.stringify(accessTokenJson))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue