feat: 添加嵌入访问限制,白名单

This commit is contained in:
shaohuzhang1 2024-03-13 16:07:13 +08:00
parent a1e2d29cf4
commit fa36e6bbab
14 changed files with 462 additions and 21 deletions

View File

@ -17,6 +17,8 @@ from django.core import cache
from django.core import signing from django.core import signing
from django.db import transaction, models from django.db import transaction, models
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpResponse
from django.template import Template, Context
from rest_framework import serializers from rest_framework import serializers
from application.models import Application, ApplicationDatasetMapping from application.models import Application, ApplicationDatasetMapping
@ -26,8 +28,10 @@ from common.constants.authentication_type import AuthenticationType
from common.db.search import get_dynamics_model, native_search, native_page_search from common.db.search import get_dynamics_model, native_search, native_page_search
from common.db.sql_execute import select_list from common.db.sql_execute import select_list
from common.exception.app_exception import AppApiException, NotFound404 from common.exception.app_exception import AppApiException, NotFound404
from common.util.common import getRestSeconds, set_embed_identity_cookie
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from common.util.file_util import get_file_content from common.util.file_util import get_file_content
from common.util.rsa_util import encrypt
from dataset.models import DataSet, Document from dataset.models import DataSet, Document
from dataset.serializers.common_serializers import list_paragraph from dataset.serializers.common_serializers import list_paragraph
from setting.models import AuthOperate from setting.models import AuthOperate
@ -38,6 +42,7 @@ from smartdoc.conf import PROJECT_DIR
from smartdoc.settings import JWT_AUTH from smartdoc.settings import JWT_AUTH
token_cache = cache.caches['token_cache'] token_cache = cache.caches['token_cache']
chat_cache = cache.caches['chat_cache']
class ModelDatasetAssociation(serializers.Serializer): class ModelDatasetAssociation(serializers.Serializer):
@ -104,6 +109,31 @@ class ApplicationSerializer(serializers.Serializer):
ModelDatasetAssociation(data={'user_id': user_id, 'model_id': self.data.get('model_id'), ModelDatasetAssociation(data={'user_id': user_id, 'model_id': self.data.get('model_id'),
'dataset_id_list': self.data.get('dataset_id_list')}).is_valid() 'dataset_id_list': self.data.get('dataset_id_list')}).is_valid()
class Embed(serializers.Serializer):
host = serializers.CharField(required=True, error_messages=ErrMessage.char("主机"))
protocol = serializers.CharField(required=True, error_messages=ErrMessage.char("协议"))
token = serializers.CharField(required=True, error_messages=ErrMessage.char("token"))
def get_embed(self, request, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
index_path = os.path.join(PROJECT_DIR, 'apps', "application", 'template', 'embed.js')
file = open(index_path, "r", encoding='utf-8')
content = file.read()
file.close()
is_auth = 'true'
try:
ApplicationSerializer.Authentication(data={'access_token': self.data.get('token')}).auth()
except Exception as e:
is_auth = 'false'
t = Template(content)
s = t.render(
Context(
{'is_auth': is_auth, 'protocol': 'http', 'host': 'localhost:8000', 'token': '0a8d892c755f1a75'}))
response = HttpResponse(s, status=200, headers={'Content-Type': 'text/javascript'})
set_embed_identity_cookie(request, response)
return response
class AccessTokenSerializer(serializers.Serializer): class AccessTokenSerializer(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.boolean("应用id")) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.boolean("应用id"))

View File

@ -24,6 +24,7 @@ from application.chat_pipeline.step.reset_problem_step.impl.base_reset_problem_s
from application.chat_pipeline.step.search_dataset_step.impl.base_search_dataset_step import BaseSearchDatasetStep from application.chat_pipeline.step.search_dataset_step.impl.base_search_dataset_step import BaseSearchDatasetStep
from application.models import ChatRecord, Chat, Application, ApplicationDatasetMapping from application.models import ChatRecord, Chat, Application, ApplicationDatasetMapping
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from common.util.field_message import ErrMessage
from common.util.rsa_util import decrypt from common.util.rsa_util import decrypt
from common.util.split_model import flat_map from common.util.split_model import flat_map
from dataset.models import Paragraph, Document from dataset.models import Paragraph, Document
@ -31,6 +32,7 @@ from setting.models import Model
from setting.models_provider.constants.model_provider_constants import ModelProvideConstants from setting.models_provider.constants.model_provider_constants import ModelProvideConstants
chat_cache = caches['model_cache'] chat_cache = caches['model_cache']
chat_embed_identity_cache = caches['chat_cache']
class ChatInfo: class ChatInfo:

View File

@ -0,0 +1,294 @@
const guideHtml=`
<div class="mask">
<div class="content"></div>
</div>
<div class="tips">
<div class="close">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z" fill="#ffffff"></path>
</svg>
</div>
<div class="title"> 🌟 遇见问题不再是障碍</div>
<p>你好我是你的智能小助手<br/>
点我开启高效解答模式让问题变成过去式</p>
<div class="button">
<button>我知道了</button>
</div>
<span class="arrow" ></span>
</div>
`
const chatButtonHtml=
`<div class="chat_button"><svg xmlns="http://www.w3.org/2000/svg" width="48" height="56" viewBox="0 0 48 56" fill="none">
<g filter="url(#filter0_d_349_49711)">
<path d="M8 24C8 12.9543 16.9543 4 28 4H48V44H28C16.9543 44 8 35.0457 8 24Z" fill="url(#paint0_linear_349_49711)"/>
</g>
<path d="M31.6667 15.6665H28.3333V18.1665H29.1667V19.8332H24.5833C23.6629 19.8332 22.9167 20.5794 22.9167 21.4998V30.6665C22.9167 31.587 23.6629 32.3332 24.5833 32.3332H35.4167C36.3371 32.3332 37.0833 31.587 37.0833 30.6665V21.4998C37.0833 20.5794 36.3371 19.8332 35.4167 19.8332H30.8333V18.1665H31.6667V15.6665ZM25.8333 24.8332H28.3333V27.3332H25.8333V24.8332ZM34.1667 24.8332V27.3332H31.6667V24.8332H34.1667Z" fill="white"/>
<path d="M21.6667 23.9998H20V28.1665H21.6667V23.9998Z" fill="white"/>
<path d="M38.3333 23.9998H40V28.1665H38.3333V23.9998Z" fill="white"/>
<defs>
<filter id="filter0_d_349_49711" x="0" y="0" width="56" height="56" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.168627 0 0 0 0 0.372549 0 0 0 0 0.85098 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_349_49711"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_349_49711" result="shape"/>
</filter>
<linearGradient id="paint0_linear_349_49711" x1="48" y1="25.6667" x2="8" y2="25.6667" gradientUnits="userSpaceOnUse">
<stop stop-color="#9258F7"/>
<stop offset="1" stop-color="#3370FF"/>
</linearGradient>
</defs>
</svg>
</div>`
const getChatContainerHtml=(protocol,host,token)=>{
return `<div id="chat_container">
<iframe id="chat" src=${protocol}://${host}/ui/chat/${token}></iframe>
<div class="closeviewport viewportnone"><svg t="1710214539671" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="rgb(100, 106, 115)" width="16" height="16"><path d="M85.333333 384c25.6 0 42.666667-17.066667 42.666667-42.666667V128h213.333333c25.6 0 42.666667-17.066667 42.666667-42.666667s-17.066667-42.666667-42.666667-42.666666H85.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666v256c0 25.6 17.066667 42.666667 42.666666 42.666667zM938.666667 640c-25.6 0-42.666667 17.066667-42.666667 42.666667v213.333333h-213.333333c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666666h256c25.6 0 42.666667-17.066667 42.666666-42.666666v-256c0-25.6-17.066667-42.666667-42.666666-42.666667zM601.6 401.066667c4.266667 8.533333 12.8 17.066667 21.333333 21.333333 4.266667 4.266667 12.8 4.266667 17.066667 4.266667h256c25.6 0 42.666667-17.066667 42.666667-42.666667s-17.066667-42.666667-42.666667-42.666667h-153.6l226.133333-226.133333c17.066667-17.066667 17.066667-42.666667 0-59.733333-8.533333-8.533333-17.066667-12.8-29.866666-12.8s-21.333333 4.266667-29.866667 12.8L682.666667 281.6V128c0-25.6-17.066667-42.666667-42.666667-42.666667s-42.666667 17.066667-42.666667 42.666667v256c0 4.266667 0 12.8 4.266667 17.066667zM115.2 968.533333L341.333333 742.4V896c0 25.6 17.066667 42.666667 42.666667 42.666667s42.666667-17.066667 42.666667-42.666667v-256c0-4.266667 0-12.8-4.266667-17.066667-4.266667-8.533333-12.8-17.066667-21.333333-21.333333-4.266667-4.266667-12.8-4.266667-17.066667-4.266667H128c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666667h153.6l-226.133333 226.133333c-17.066667 17.066667-17.066667 42.666667 0 59.733333s42.666667 17.066667 59.733333 0z" p-id="10189"></path></svg></div>
<div class="openviewport">
<svg t="1710150885892" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="rgb(100, 106, 115)" width="16" height="16" ><path d="M85.333333 384c25.6 0 42.666667-17.066667 42.666667-42.666667V128h213.333333c25.6 0 42.666667-17.066667 42.666667-42.666667s-17.066667-42.666667-42.666667-42.666666H85.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666v256c0 25.6 17.066667 42.666667 42.666666 42.666667zM938.666667 640c-25.6 0-42.666667 17.066667-42.666667 42.666667v213.333333h-213.333333c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666666h256c25.6 0 42.666667-17.066667 42.666666-42.666666v-256c0-25.6-17.066667-42.666667-42.666666-42.666667zM977.066667 68.266667c-4.266667-8.533333-12.8-17.066667-21.333334-21.333334-4.266667-4.266667-12.8-4.266667-17.066666-4.266666h-256c-25.6 0-42.666667 17.066667-42.666667 42.666666s17.066667 42.666667 42.666667 42.666667h153.6l-226.133334 226.133333c-17.066667 17.066667-17.066667 42.666667 0 59.733334 8.533333 8.533333 17.066667 12.8 29.866667 12.8s21.333333-4.266667 29.866667-12.8L896 187.733333V341.333333c0 25.6 17.066667 42.666667 42.666667 42.666667s42.666667-17.066667 42.666666-42.666667V85.333333c0-4.266667 0-12.8-4.266666-17.066666zM354.133333 610.133333L128 836.266667V682.666667c0-25.6-17.066667-42.666667-42.666667-42.666667s-42.666667 17.066667-42.666666 42.666667v256c0 4.266667 0 12.8 4.266666 17.066666 4.266667 8.533333 12.8 17.066667 21.333334 21.333334 4.266667 4.266667 12.8 4.266667 17.066666 4.266666h256c25.6 0 42.666667-17.066667 42.666667-42.666666s-17.066667-42.666667-42.666667-42.666667H187.733333l226.133334-226.133333c17.066667-17.066667 17.066667-42.666667 0-59.733334s-42.666667-17.066667-59.733334 0z" p-id="8645"></path></svg>
</div>
<div class="close"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z" fill="#646A73"/>
</svg>
</div>`
}
/**
* 初始化引导
* @param {*} root
*/
const initGuide=(root)=>{
root.insertAdjacentHTML("beforeend",guideHtml)
const button=root.querySelector(".button")
const close_icon=root.querySelector('.close')
const close_func=()=>{
root.removeChild(root.querySelector('.tips'))
root.removeChild(root.querySelector('.mask'))
localStorage.setItem('maxkbMaskTip',true)
}
button.onclick=close_func
close_icon.onclick=close_func
}
const initChat=(root)=>{
// 添加对话icon
root.insertAdjacentHTML("beforeend",chatButtonHtml)
// 添加对话框
root.insertAdjacentHTML('beforeend',getChatContainerHtml('{{protocol}}','{{host}}','{{token}}'))
// 按钮元素
const chat_button=root.querySelector('.chat_button')
// 对话框元素
const chat_container=root.querySelector('#chat_container')
const viewport=root.querySelector('.openviewport')
const closeviewport=root.querySelector('.closeviewport')
const close_func=()=>{
chat_container.style['display']=chat_container.style['display']=='block'?'none':'block'
}
close_icon=chat_container.querySelector('.close')
chat_button.onclick = close_func
close_icon.onclick=close_func
const viewport_func=()=>{
if(chat_container.classList.contains('enlarge')){
chat_container.classList.remove("enlarge");
viewport.classList.remove('viewportnone')
closeviewport.classList.add('viewportnone')
}else{
chat_container.classList.add("enlarge");
viewport.classList.add('viewportnone')
closeviewport.classList.remove('viewportnone')
}
}
viewport.onclick=viewport_func
closeviewport.onclick=viewport_func
}
/**
* 第一次进来的引导提示
*/
function initMaxkb(){
initMaxkbStyle()
const root=document.createElement('div')
root.id="maxkb"
document.body.appendChild(root)
const maxkbMaskTip=localStorage.getItem('maxkbMaskTip')
if(maxkbMaskTip==null){
initGuide(root)
}
initChat(root)
}
// 初始化全局样式
function initMaxkbStyle(){
style=document.createElement('style')
style.type='text/css'
style.innerText= `/* 放大 */
#maxkb .enlarge {
width: 50%!important;
height: 100%!important;
bottom: 0!important;
right: 0 !important;
}
@media only screen and (max-width: 768px){
#maxkb .enlarge {
width: 100%!important;
height: 100%!important;
right: 0 !important;
bottom: 0!important;
}
}
/* 引导 */
#maxkb .mask {
position: fixed;
z-index: 999;
background-color: transparent;
height: 100%;
width: 100%;
top: 0;
left: 0;
}
#maxkb .mask .content {
width: 45px;
height: 50px;
box-shadow: 1px 1px 1px 2000px rgba(0,0,0,.6);
border-radius: 50% 0 0 50%;
position: absolute;
right: 0;
bottom: 42px;
z-index: 1000;
}
#maxkb .tips {
position: absolute;
bottom: 30px;
right: 60px;
padding: 22px 24px 24px;
border-radius: 6px;
color: #ffffff;
font-size: 14px;
background: #3370FF;
z-index: 1000;
}
#maxkb .tips .arrow {
position: absolute;
background: #3370FF;
width: 10px;
height: 10px;
pointer-events: none;
transform: rotate(45deg);
box-sizing: border-box;
/* left */
right: -5px;
bottom: 33px;
border-left-color: transparent;
border-bottom-color: transparent
}
#maxkb .tips .title {
font-size: 20px;
font-weight: 500;
margin-bottom: 8px;
}
#maxkb .tips .button {
text-align: right;
margin-top: 24px;
}
#maxkb .tips .button button {
border-radius: 4px;
background: #FFF;
padding: 3px 12px;
color: #3370FF;
cursor: pointer;
outline: none;
border: none;
}
#maxkb .tips .button button::after{
border: none;
}
#maxkb .tips .close {
position: absolute;
right: 20px;
top: 20px;
cursor: pointer;
}
#chat_container {
width: 420px;
height: 600px;
display:none;
}
@media only screen and (max-width: 768px) {
#chat_container {
width: 100%;
height: 70%;
right: 0 !important;
}
}
#maxkb .chat_button{
position: fixed;
bottom: 30px;
right: 0;
cursor: pointer;
}
#maxkb #chat_container{
z-index:10000;position: relative;
border-radius: 8px;
border: 1px solid var(--N300, #DEE0E3);
background: linear-gradient(188deg, rgba(235, 241, 255, 0.20) 39.6%, rgba(231, 249, 255, 0.20) 94.3%), #EFF0F1;
box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.10);
position: fixed;bottom: 20px;right: 45px;overflow: hidden;
}
#maxkb #chat_container .close{
position: absolute;
top: 15px;
right: 10px;
cursor: pointer;
}
#maxkb #chat_container .openviewport{
position: absolute;
top: 15px;
right: 50px;
cursor: pointer;
}
#maxkb #chat_container .closeviewport{
position: absolute;
top: 15px;
right: 50px;
cursor: pointer;
}
#maxkb #chat_container .viewportnone{
display:none;
}
#maxkb #chat_container #chat{
height:100%;
width:100%;
border: none;
}
#maxkb #chat_container {
animation: appear .4s ease-in-out;
}
@keyframes appear {
from {
height: 0;;
}
to {
height: 600px;
}
}`
document.head.appendChild(style)
}
function embedChatbot() {
if ({{is_auth}}) {
// 初始化maxkb智能小助手
initMaxkb()
} else console.error('invalid parameter')
}
window.onload = embedChatbot

View File

@ -6,6 +6,7 @@ app_name = "application"
urlpatterns = [ urlpatterns = [
path('application', views.Application.as_view(), name="application"), path('application', views.Application.as_view(), name="application"),
path('application/profile', views.Application.Profile.as_view()), path('application/profile', views.Application.Profile.as_view()),
path('application/embed', views.Application.Embed.as_view()),
path('application/authentication', views.Application.Authentication.as_view()), path('application/authentication', views.Application.Authentication.as_view()),
path('application/<str:application_id>/model', views.Application.Model.as_view()), path('application/<str:application_id>/model', views.Application.Model.as_view()),
path('application/<str:application_id>/hit_test', views.Application.HitTest.as_view()), path('application/<str:application_id>/hit_test', views.Application.HitTest.as_view()),

View File

@ -6,6 +6,7 @@
@date2023/10/27 14:56 @date2023/10/27 14:56
@desc: @desc:
""" """
from django.http import HttpResponse from django.http import HttpResponse
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action from rest_framework.decorators import action
@ -20,13 +21,24 @@ from common.constants.permission_constants import CompareConstants, PermissionCo
from common.exception.app_exception import AppAuthenticationFailed from common.exception.app_exception import AppAuthenticationFailed
from common.response import result from common.response import result
from common.swagger_api.common_api import CommonApi from common.swagger_api.common_api import CommonApi
from common.util.common import query_params_to_single_dict from common.util.common import query_params_to_single_dict, set_embed_identity_cookie
from dataset.serializers.dataset_serializers import DataSetSerializers from dataset.serializers.dataset_serializers import DataSetSerializers
class Application(APIView): class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
class Embed(APIView):
@action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary="获取嵌入js",
operation_id="获取嵌入js",
tags=["应用"],
manual_parameters=ApplicationApi.ApiKey.get_request_params_api())
def get(self, request: Request):
return ApplicationSerializer.Embed(
data={'protocol': request.query_params.get('protocol'), 'token': request.query_params.get('token'),
'host': request.query_params.get('host'), }).get_embed(request)
class Model(APIView): class Model(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@ -185,7 +197,7 @@ class Application(APIView):
"Access-Control-Allow-Methods": "POST", "Access-Control-Allow-Methods": "POST",
"Access-Control-Allow-Headers": "Origin,Content-Type,Cookie,Accept,Token"} "Access-Control-Allow-Headers": "Origin,Content-Type,Cookie,Accept,Token"}
) )
set_embed_identity_cookie(request, response)
return response return response
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)

View File

@ -18,7 +18,7 @@ from common.auth import TokenAuth, has_permissions
from common.constants.permission_constants import Permission, Group, Operate, \ from common.constants.permission_constants import Permission, Group, Operate, \
RoleConstants, ViewPermission, CompareConstants RoleConstants, ViewPermission, CompareConstants
from common.response import result from common.response import result
from common.util.common import query_params_to_single_dict from common.util.common import query_params_to_single_dict, set_embed_identity_cookie
class ChatView(APIView): class ChatView(APIView):
@ -71,9 +71,13 @@ class ChatView(APIView):
dynamic_tag=keywords.get('application_id'))]) dynamic_tag=keywords.get('application_id'))])
) )
def post(self, request: Request, chat_id: str): def post(self, request: Request, chat_id: str):
return ChatMessageSerializer(data={'chat_id': chat_id}).chat(request.data.get('message'), request.data.get( response = ChatMessageSerializer(data={'chat_id': chat_id}).chat(request.data.get('message'),
're_chat') if 're_chat' in request.data else False, request.data.get( request.data.get(
'stream') if 'stream' in request.data else True) 're_chat') if 're_chat' in request.data else False,
request.data.get(
'stream') if 'stream' in request.data else True)
set_embed_identity_cookie(request, response)
return response
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary="获取对话列表", @swagger_auto_schema(operation_summary="获取对话列表",

View File

@ -6,21 +6,28 @@
@date2023/9/4 11:16 @date2023/9/4 11:16
@desc: 认证类 @desc: 认证类
""" """
import datetime
import traceback
from urllib.parse import urlparse
from django.core import cache from django.core import cache
from django.core import signing from django.core import signing
from django.db.models import QuerySet from django.db.models import QuerySet
from ipware import get_client_ip
from rest_framework.authentication import TokenAuthentication from rest_framework.authentication import TokenAuthentication
from application.models.api_key_model import ApplicationAccessToken, ApplicationApiKey from application.models.api_key_model import ApplicationAccessToken, ApplicationApiKey
from common.constants.authentication_type import AuthenticationType from common.constants.authentication_type import AuthenticationType
from common.constants.permission_constants import Auth, get_permission_list_by_role, RoleConstants, Permission, Group, \ from common.constants.permission_constants import Auth, get_permission_list_by_role, RoleConstants, Permission, Group, \
Operate Operate
from common.exception.app_exception import AppAuthenticationFailed from common.exception.app_exception import AppAuthenticationFailed, AppEmbedIdentityFailed, AppChatNumOutOfBoundsFailed
from common.util.common import getRestSeconds
from common.util.rsa_util import decrypt
from smartdoc.settings import JWT_AUTH from smartdoc.settings import JWT_AUTH
from users.models.user import User, get_user_dynamics_permission from users.models.user import User, get_user_dynamics_permission
token_cache = cache.caches['token_cache'] token_cache = cache.caches['token_cache']
chat_cache = cache.caches['chat_cache']
class AnonymousAuthentication(TokenAuthentication): class AnonymousAuthentication(TokenAuthentication):
@ -80,6 +87,35 @@ class TokenAuth(TokenAuthentication):
raise AppAuthenticationFailed(1002, "身份验证信息不正确") raise AppAuthenticationFailed(1002, "身份验证信息不正确")
if not application_access_token.access_token == auth_details.get('access_token'): if not application_access_token.access_token == auth_details.get('access_token'):
raise AppAuthenticationFailed(1002, "身份验证信息不正确") raise AppAuthenticationFailed(1002, "身份验证信息不正确")
if application_access_token.white_active:
referer = request.META.get('HTTP_REFERER')
if referer is not None:
client_ip = urlparse(referer).hostname
else:
client_ip = get_client_ip(request)
if not application_access_token.white_list.__contains__(client_ip):
raise AppAuthenticationFailed(1002, "身份验证信息不正确")
if 'embed_identity' in request.COOKIES and request.path.__contains__('/api/application/chat_message/'):
embed_identity = request.COOKIES['embed_identity']
try:
# 如果无法解密 说明embed_identity并非系统颁发
value = decrypt(embed_identity)
except Exception as e:
raise AppEmbedIdentityFailed(1004, '嵌入cookie不正确')
embed_identity_number = chat_cache.get(value)
if embed_identity_number is not None:
if application_access_token.access_num <= embed_identity_number:
raise AppChatNumOutOfBoundsFailed(1003, '访问次数超过今日访问量')
# 对话次数+1
try:
if not chat_cache.incr(value):
# 如果修改失败则设置为1
chat_cache.set(value, 1,
timeout=getRestSeconds())
except Exception as e:
# 如果修改失败则设置为1 证明 key不存在
chat_cache.add(value, 1,
timeout=getRestSeconds())
return application_access_token.application.user, Auth( return application_access_token.application.user, Auth(
role_list=[RoleConstants.APPLICATION_ACCESS_TOKEN], role_list=[RoleConstants.APPLICATION_ACCESS_TOKEN],
permission_list=[ permission_list=[
@ -94,4 +130,7 @@ class TokenAuth(TokenAuthentication):
raise AppAuthenticationFailed(1002, "身份验证信息不正确!非法用户") raise AppAuthenticationFailed(1002, "身份验证信息不正确!非法用户")
except Exception as e: except Exception as e:
traceback.format_exc()
if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed):
raise e
raise AppAuthenticationFailed(1002, "身份验证信息不正确!非法用户") raise AppAuthenticationFailed(1002, "身份验证信息不正确!非法用户")

View File

@ -51,3 +51,25 @@ class AppUnauthorizedFailed(AppApiException):
def __init__(self, code, message): def __init__(self, code, message):
self.code = code self.code = code
self.message = message self.message = message
class AppEmbedIdentityFailed(AppApiException):
"""
嵌入cookie异常
"""
status_code = 460
def __init__(self, code, message):
self.code = code
self.message = message
class AppChatNumOutOfBoundsFailed(AppApiException):
"""
访问次数超过今日访问量
"""
status_code = 461
def __init__(self, code, message):
self.code = code
self.message = message

View File

@ -6,9 +6,36 @@
@date2023/10/16 16:42 @date2023/10/16 16:42
@desc: @desc:
""" """
import datetime
import importlib import importlib
import uuid
from functools import reduce from functools import reduce
from typing import Dict, List from typing import Dict, List
from django.core import cache
from .rsa_util import encrypt
chat_cache = cache.caches['chat_cache']
def set_embed_identity_cookie(request, response):
if 'embed_identity' in request.COOKIES:
embed_identity = request.COOKIES['embed_identity']
else:
value = str(uuid.uuid1())
embed_identity = encrypt(value)
chat_cache.set(value, 0, timeout=getRestSeconds())
response.set_cookie("embed_identity", embed_identity, max_age=3600 * 24 * 100, samesite='None',
secure=True)
return response
def getRestSeconds():
now = datetime.datetime.now()
today_begin = datetime.datetime(now.year, now.month, now.day, 0, 0, 0)
tomorrow_begin = today_begin + datetime.timedelta(days=1)
rest_seconds = (tomorrow_begin - now).seconds
return rest_seconds
def sub_array(array: List, item_num=10): def sub_array(array: List, item_num=10):

View File

@ -103,8 +103,7 @@ CACHES = {
'LOCATION': os.path.join(PROJECT_DIR, 'data', 'cache', "token_cache") # 文件夹路径 'LOCATION': os.path.join(PROJECT_DIR, 'data', 'cache', "token_cache") # 文件夹路径
}, },
"chat_cache": { "chat_cache": {
'BACKEND': 'common.cache.file_cache.FileCache', 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': os.path.join(PROJECT_DIR, 'data', 'cache', "chat_cache") # 文件夹路径
} }
} }

View File

@ -28,6 +28,7 @@ pycryptodome = "^3.19.0"
beautifulsoup4 = "^4.12.2" beautifulsoup4 = "^4.12.2"
html2text = "^2024.2.26" html2text = "^2024.2.26"
langchain-openai = "^0.0.8" langchain-openai = "^0.0.8"
django-ipware = "^6.0.4"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]

View File

@ -399,10 +399,10 @@ const getWrite = (chat: any, reader: any, stream: boolean) => {
} }
return stream ? write_stream : write_json return stream ? write_stream : write_json
} }
const errorWrite = (chat: any) => { const errorWrite = (chat: any, message?: string) => {
ChatManagement.addChatRecord(chat, 50, loading) ChatManagement.addChatRecord(chat, 50, loading)
ChatManagement.write(chat.id) ChatManagement.write(chat.id)
ChatManagement.append(chat.id, '抱歉,当前正在维护,无法提供服务,请稍后再试!') ChatManagement.append(chat.id, message || '抱歉,当前正在维护,无法提供服务,请稍后再试!')
ChatManagement.close(chat.id) ChatManagement.close(chat.id)
} }
function chatMessage(chat?: any, problem?: string) { function chatMessage(chat?: any, problem?: string) {
@ -444,6 +444,10 @@ function chatMessage(chat?: any, problem?: string) {
.catch((err) => { .catch((err) => {
errorWrite(chat) errorWrite(chat)
}) })
} else if (response.status === 460) {
return Promise.reject('无法识别用户身份')
} else if (response.status === 461) {
return Promise.reject('抱歉,您的提问已达到最大限制,请明天再来吧!')
} else { } else {
nextTick(() => { nextTick(() => {
// //
@ -468,7 +472,7 @@ function chatMessage(chat?: any, problem?: string) {
ChatManagement.close(chat.id) ChatManagement.close(chat.id)
}) })
.catch((e: any) => { .catch((e: any) => {
MsgError(e) errorWrite(chat, e + '')
}) })
} }
} }

View File

@ -58,7 +58,10 @@ instance.interceptors.response.use(
} }
} }
if (err.response?.status === 401) { if (err.response?.status === 401) {
if (!err.response.config.url.includes('chat/open')) { if (
!err.response.config.url.includes('chat/open') &&
!err.response.config.url.includes('application/profile')
) {
router.push({ name: 'login' }) router.push({ name: 'login' })
} }
} }

View File

@ -29,7 +29,7 @@
<AppIcon iconName="app-copy"></AppIcon> <AppIcon iconName="app-copy"></AppIcon>
</el-button> </el-button>
</div> </div>
<div class="mt-8"> <div class="mt-8 white-space">
{{ source2 }} {{ source2 }}
</div> </div>
</div> </div>
@ -68,13 +68,13 @@ frameborder="0"
allow="microphone"> allow="microphone">
</iframe> </iframe>
` `
source2.value = `<script> window.maxkbChatConfig = {
token: "${val}", source2.value = `
host: "${window.location.host}", <script src="${
protocol:"${window.location.protocol}" window.location.origin
} }/api/application/embed?protocol=${window.location.protocol.replace(':', '')}&host=${
<\/script> window.location.host
<script src="${window.location.origin}/ui/embeb.js"> }&token=${val}">
<\/script> <\/script>
` `
dialogVisible.value = true dialogVisible.value = true
@ -95,6 +95,9 @@ defineExpose({ open })
font-size: 13px; font-size: 13px;
white-space: pre; white-space: pre;
height: 180px; height: 180px;
.white-space {
white-space: pre-wrap;
}
} }
} }
</style> </style>