MGLDZM commited on
Commit
72092ce
·
1 Parent(s): ea0a765
main.py CHANGED
@@ -25,7 +25,7 @@ async def main_page(request: Request, user: User = Depends(User.find_from_cookie
25
  return RedirectResponse(url='/hold')
26
 
27
  tools = [{"name": k, "desc": v} for k, v in chat_functions.function_user_text.items()]
28
- token = user.create_token()
29
  response = templates.TemplateResponse(
30
  "main.html", {
31
  "request": request,
@@ -33,7 +33,6 @@ async def main_page(request: Request, user: User = Depends(User.find_from_cookie
33
  "version": fecha_unix,
34
  "tools": tools
35
  })
36
- user.update()
37
  return response
38
 
39
 
@@ -49,18 +48,18 @@ async def validate_oauth(request: Request):
49
  params = dict(request.query_params)
50
  fingerprint = params["state"].split("=",1)[1]
51
  google_userinfo = oauth.validate_redirect(params) | {"fprint": fingerprint}
52
- user = model.User.find_or_create(google_userinfo)
53
- token = user.create_token()
54
  response = RedirectResponse(url='/')
55
  response.set_cookie(key="token", value=token)
56
- user.update()
57
  return response
58
 
59
 
60
  ########################## API ##########################
61
 
62
  @app.post("/getConfigs")
63
- async def get_configs(request: Request, user = Depends(model.User.find_from_header)):
64
  tokens = user.tokens
65
  year, month = datetime.now().strftime("%y-%m").split("-")
66
  mes = sum(tokens.get(year, {}).get(month, {"_":0}).values())
@@ -68,27 +67,24 @@ async def get_configs(request: Request, user = Depends(model.User.find_from_head
68
  return user.configs.model_dump() | {"tokens": tokens}
69
 
70
  @app.post("/setConfigs")
71
- async def set_configs(request: Request, configs: model.Configs, user = Depends(User.find_from_header)):
72
  user.configs = configs
73
  user.update()
74
  return {"success":True}
75
 
76
 
77
  @app.post("/getToken")
78
- async def get_token(request: Request, data: dict, user: User = Depends(User.find_from_header)):
79
- user.session = model.Session(**data)
80
- data.pop("fingerprint")
81
- security.validate_signature(**data)
82
- security.create_jwt_token(data)
83
- token = user.create_token()
84
- class Session(BaseModel):
85
- gid: str
86
- fingerprint: str
87
- public_key: str
88
- return {"su":True}
89
 
90
  @app.post("/chat")
91
- async def chat_async(request: Request, body: model.Chat, user = Depends(model.User.find_from_header)):
92
  if(len(body.messages) < 1 or body.messages[-1].content==""):
93
  log_module.logger.warning(user.gid, "Mensaje RQ", "error, mensajes vacios")
94
  raise HTTPException(
@@ -106,20 +102,26 @@ async def chat_async(request: Request, body: model.Chat, user = Depends(model.Us
106
  async def privacy_policy(request: Request):
107
  return templates.TemplateResponse("PrivacyPolicy.html", {"request": request})
108
 
109
- @app.route("/hold", ["GET", "POST"])
110
- async def on_hold(request: Request):
111
  redirect_to_login = RedirectResponse(url='/login')
112
 
113
- user = model.User.find_from_token(request, "view")
114
-
115
  if user.can_use("chat"):
116
  return redirect_to_login
117
-
118
- if request.method=="POST" and not user.description:
 
 
 
 
 
 
 
 
119
  form = await request.form()
120
  if message := form["message"].strip():
121
  user.update_description(message)
122
-
123
  return templates.TemplateResponse(
124
  "no_access.html", {
125
  "request": request,
 
25
  return RedirectResponse(url='/hold')
26
 
27
  tools = [{"name": k, "desc": v} for k, v in chat_functions.function_user_text.items()]
28
+ token = user.create_shortlived_token()
29
  response = templates.TemplateResponse(
30
  "main.html", {
31
  "request": request,
 
33
  "version": fecha_unix,
34
  "tools": tools
35
  })
 
36
  return response
37
 
38
 
 
48
  params = dict(request.query_params)
49
  fingerprint = params["state"].split("=",1)[1]
50
  google_userinfo = oauth.validate_redirect(params) | {"fprint": fingerprint}
51
+ user:User = model.User.find_or_create(google_userinfo)
52
+ token = user.create_cookie()
53
  response = RedirectResponse(url='/')
54
  response.set_cookie(key="token", value=token)
55
+ user.update_user()
56
  return response
57
 
58
 
59
  ########################## API ##########################
60
 
61
  @app.post("/getConfigs")
62
+ async def get_configs(request: Request, user ): #= Depends(model.User.find_from_header)):
63
  tokens = user.tokens
64
  year, month = datetime.now().strftime("%y-%m").split("-")
65
  mes = sum(tokens.get(year, {}).get(month, {"_":0}).values())
 
67
  return user.configs.model_dump() | {"tokens": tokens}
68
 
69
  @app.post("/setConfigs")
70
+ async def set_configs(request: Request, configs: model.Configs, user ): # = Depends(User.find_from_header)):
71
  user.configs = configs
72
  user.update()
73
  return {"success":True}
74
 
75
 
76
  @app.post("/getToken")
77
+ async def get_token(request: Request, data: model.APISession = Depends(model.APISession.validate)):
78
+ # user.session = model.Session(**data)
79
+ # data.pop("fingerprint")
80
+ # security.validate_signature(**data)
81
+ # security.create_jwt_token(data)
82
+ # token = user.create_token()
83
+ pass
84
+
 
 
 
85
 
86
  @app.post("/chat")
87
+ async def chat_async(request: Request, body: model.Chat, user ): # = Depends(model.User.find_from_header)):
88
  if(len(body.messages) < 1 or body.messages[-1].content==""):
89
  log_module.logger.warning(user.gid, "Mensaje RQ", "error, mensajes vacios")
90
  raise HTTPException(
 
102
  async def privacy_policy(request: Request):
103
  return templates.TemplateResponse("PrivacyPolicy.html", {"request": request})
104
 
105
+ @app.get("/hold") #, ["GET", "POST"]
106
+ async def on_hold(request: Request, user: User = Depends(User.find_from_cookie)):
107
  redirect_to_login = RedirectResponse(url='/login')
108
 
 
 
109
  if user.can_use("chat"):
110
  return redirect_to_login
111
+
112
+ return templates.TemplateResponse(
113
+ "no_access.html", {
114
+ "request": request,
115
+ "description": bool(user.description)
116
+ })
117
+
118
+ @app.post("/hold") #, ["GET", "POST"]
119
+ async def on_hold(request: Request, user: User = Depends(User.find_from_cookie)):
120
+ if not user.description:
121
  form = await request.form()
122
  if message := form["message"].strip():
123
  user.update_description(message)
124
+
125
  return templates.TemplateResponse(
126
  "no_access.html", {
127
  "request": request,
modules/model.py CHANGED
@@ -1,4 +1,6 @@
1
- import os
 
 
2
  from pymongo.mongo_client import MongoClient
3
  from pymongo.server_api import ServerApi
4
  from fastapi import Request
@@ -60,47 +62,44 @@ class Chat(BaseModel):
60
  def new_msg(self: Self):
61
  return Message(role="", content="")
62
 
63
- class Session(BaseModel):
64
  gid: str
65
- fingerprint: str
66
  public_key: str
67
  guid: str = str(uuid.uuid4())
68
- created: datetime = datetime.now(tz)
69
- updated: datetime = datetime.now(tz)
70
- expire: datetime = datetime.now(tz)+timedelta(days=7)
71
-
72
-
73
-
74
- @classmethod
75
- def remove_expired(cls: Self, gid: str) -> None:
76
- DB.sess.delete_one({"gid": gid, "expire": {"$lt": datetime.now(tz)}})
77
 
78
- @classmethod
79
- def clean_other_sessions(cls:Self, gid: str) -> None:
80
- count = DB.sess.count_documents({"gid": gid})
81
- if count > 3:
82
- ids_a_borrar = [
83
- registro["_id"] for registro in DB.sess.find({"gid": gid}).sort({"expire": -1}).skip(3)
84
- ]
85
- DB.sess.delete_many({"_id": {"$in": ids_a_borrar}})
86
 
87
  @classmethod
88
- def find(cls: Self, gid: str, guid: str) -> Self | None:
89
- cls.remove_expired(gid)
90
- sessj: dict = DB.sess.find_one({"gid": gid, "guid": guid})
91
- if sessj:
92
- sess: Self = cls(**sessj)
93
- sess.updated = datetime.now(tz)
94
- sess.expire = datetime.now(tz)+timedelta(days=7)
95
- cls.clean_other_sessions(gid)
96
- DB.sess.update_one({"gid": gid, "guid": guid}, {"$set": sess.model_dump()})
97
- return sess
 
 
98
 
99
- @classmethod
100
- def create(cls: Self, **kwargs: dict) -> Self:
101
- temp:Self = cls(**kwargs)
102
- DB.sess.insert_one(temp.model_dump())
103
- return temp
 
 
 
 
104
 
105
 
106
  class User(BaseModel):
@@ -112,8 +111,8 @@ class User(BaseModel):
112
  email: str
113
  gid: str
114
  role: str = "on hold"
 
115
  configs: Configs = Configs()
116
- session: Session | None = None
117
 
118
  @classmethod
119
  def find(cls: Self, gid: str) -> Self | None:
@@ -124,56 +123,35 @@ class User(BaseModel):
124
 
125
  @classmethod
126
  def find_or_create(cls: Self, data: dict)-> Self:
127
- sess:dict = Session.create(gid=data["gid"], fprint=data.pop("fprint"))
 
 
128
  found:Self = cls.find(data["gid"])
129
  if found:
130
- found.session = sess
131
  return found
132
  user:Self = cls(**data)
133
- user.session = sess
134
-
135
- DB.user.insert_one(user.model_dump(exclude={"session"}))
136
  log_module.logger.info(f"User created: {user.gid}")
137
  return user
138
 
139
-
140
- @classmethod
141
- def find_from_header(cls:Self, request: Request) -> Self:
142
- temp = cls.find_from_token(request, "api")
143
- return temp
144
-
 
 
145
  @classmethod
146
  def find_from_cookie(cls:Self, request: Request) -> Self:
147
- return cls.find_from_token(request, "view")
 
148
 
149
- @classmethod
150
- def find_from_token(cls, request: Request, usage: str) -> Self:
151
- if usage=="view":
152
- data:dict = security.token_from_cookie(request)
153
- else:
154
- data:dict = security.token_from_headers(request)
155
-
156
- userj:dict = DB.user.find_one({"gid":data["gid"]})
157
-
158
- if gid := userj.get("gid", None):
159
- sess:Session = Session.find(gid, data["guid"])
160
-
161
- if not gid or not sess:
162
- security.raise_307()
163
-
164
- user:Self = cls(**userj)
165
- user.session = sess
166
- return user
167
-
168
-
169
- def create_token(self: Self) -> str:
170
- return security.create_jwt_token({
171
- "gid": self.gid,
172
- "guid": self.session.guid,
173
- "fp": self.session.fprint,
174
- "pubk": self.session.public_key
175
- })
176
-
177
 
178
  def update_description(self: Self, message: str) -> None:
179
  log_module.logger.info(f"Description Updated: {self.gid}")
@@ -186,25 +164,11 @@ class User(BaseModel):
186
  def can_use(self: Self, activity: str):
187
  return security.can_use(self.role, activity)
188
 
189
- def update(self: Self, message: Message = None) -> None:
190
- count_tokens:int = 0
191
- if message:
192
- count_tokens:int = message.tokensOutput+message.tokensPrompt
193
- year, month, day = datetime.now().strftime("%y-%m-%d").split("-")
194
-
195
- if year not in self.tokens:
196
- self.tokens[year] = {}
197
-
198
- if month not in self.tokens[year]:
199
- self.tokens[year][month] = {}
200
-
201
- if day not in self.tokens[year][month] :
202
- self.tokens[year][month][day] = 0
203
-
204
- self.tokens[year][month][day] += count_tokens
205
-
206
  DB.user.update_one({"gid": self.gid}, {"$set": self.model_dump(exclude={"guid", "session"})})
207
 
208
-
209
-
210
-
 
 
 
1
+ import os
2
+ from typing_extensions import Unpack
3
+ from pydantic.config import ConfigDict
4
  from pymongo.mongo_client import MongoClient
5
  from pymongo.server_api import ServerApi
6
  from fastapi import Request
 
62
  def new_msg(self: Self):
63
  return Message(role="", content="")
64
 
65
+ class APISession(BaseModel):
66
  gid: str
67
+ fprint: str
68
  public_key: str
69
  guid: str = str(uuid.uuid4())
70
+ challenge: str = str(uuid.uuid4())
 
 
 
 
 
 
 
 
71
 
72
+ def __init__(self, **kwargs: dict):
73
+ kwargs["fprint"] = security.sha256(kwargs["fprint"])
74
+
75
+ super(APISession, self).__init__(**kwargs)
76
+
77
+
78
+
 
79
 
80
  @classmethod
81
+ def validate(cls: Self, request: Request, data: dict):
82
+ token_data:dict = security.token_from_headers(request)
83
+ if token_data["step"] != "intermediate":
84
+ security.raise_401()
85
+ token = request.headers.get("Autorization", " ").split(" ",1)[1]
86
+ fprint = security.sha256(data["fingerprint"])
87
+ security.validate_signature(data["public_key"],data["signature"], token)
88
+ if fprint != token_data["fprint"]:
89
+ security.raise_401()
90
+
91
+ new_obj = cls(**data)
92
+ return new_obj
93
 
94
+ def validate_signature(self: Self, signature:str):
95
+ valid = security.validate_signature(self.public_key, signature, self.challenge)
96
+ if not valid:
97
+ security.raise_401()
98
+ return True
99
+
100
+ def create_token(self: Self):
101
+ self.challenge = str(uuid.uuid4())
102
+ return security.create_jwt_token(self.model_dump())
103
 
104
 
105
  class User(BaseModel):
 
111
  email: str
112
  gid: str
113
  role: str = "on hold"
114
+ fprint: str = ""
115
  configs: Configs = Configs()
 
116
 
117
  @classmethod
118
  def find(cls: Self, gid: str) -> Self | None:
 
123
 
124
  @classmethod
125
  def find_or_create(cls: Self, data: dict)-> Self:
126
+ if not data.get("fprint", None):
127
+ security.raise_307()
128
+ data["fprint"] = security.sha256(data["fprint"])
129
  found:Self = cls.find(data["gid"])
130
  if found:
131
+ found.fprint = data["fprint"]
132
  return found
133
  user:Self = cls(**data)
134
+ DB.user.insert_one(user.model_dump(exclude={"session", "fprint"}))
 
 
135
  log_module.logger.info(f"User created: {user.gid}")
136
  return user
137
 
138
+ def create_cookie(self: Self) -> str:
139
+ return security.create_jwt_token({"gid": self.gid, "fprint":self.fprint })
140
+
141
+ def create_shortlived_token(self: Self) -> str:
142
+ return security.create_jwt_token({"gid": self.gid, "fprint":self.fprint, "step":"intermediate" }, maxlife=3)
143
+
144
+
145
+
146
  @classmethod
147
  def find_from_cookie(cls:Self, request: Request) -> Self:
148
+ data:dict = security.token_from_cookie(request)
149
+ user:dict = DB.user.find_one({"gid":data["gid"]})
150
 
151
+ if (user.get("gid", None)):
152
+ return cls(**user)
153
+
154
+ security.raise_307()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
  def update_description(self: Self, message: str) -> None:
157
  log_module.logger.info(f"Description Updated: {self.gid}")
 
164
  def can_use(self: Self, activity: str):
165
  return security.can_use(self.role, activity)
166
 
167
+ def update_user(self: Self, message: Message = None) -> None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  DB.user.update_one({"gid": self.gid}, {"$set": self.model_dump(exclude={"guid", "session"})})
169
 
170
+ @staticmethod
171
+ def update_usage(gid:str, message:Message):
172
+ count_tokens:int = message.tokensOutput+message.tokensPrompt
173
+ inc_field = datetime.now().strftime("tokens.%y.%m.%d")
174
+ DB.user.update_one({"gid": gid}, {"$inc":{inc_field: count_tokens}})
modules/security.py CHANGED
@@ -19,10 +19,9 @@ for key in settings.USERS:
19
 
20
 
21
 
22
- def create_jwt_token(data):
23
- to_encode = data
24
- expire = datetime.utcnow() + timedelta(minutes=settings.JWT_EXPIRATION_TIME_MINUTES_API)
25
- to_encode.update({"exp": expire})
26
  encoded_jwt = jwt.encode(to_encode, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM)
27
  return encoded_jwt
28
 
@@ -79,5 +78,10 @@ def validate_signature(public_key: str, signature: str, data:str) -> bool:
79
  pss.new(public_key).verify(data_, signature)
80
  return True
81
  except ValueError:
82
- return False
83
-
 
 
 
 
 
 
19
 
20
 
21
 
22
+ def create_jwt_token(data, maxlife=settings.JWT_EXPIRATION_TIME_MINUTES_API):
23
+ expire = datetime.utcnow() + timedelta(minutes=maxlife)
24
+ to_encode = data | {"exp": expire}
 
25
  encoded_jwt = jwt.encode(to_encode, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM)
26
  return encoded_jwt
27
 
 
78
  pss.new(public_key).verify(data_, signature)
79
  return True
80
  except ValueError:
81
+ raise_401()
82
+
83
+ def sha256(data:str|bytes) -> str:
84
+ data_ = data if isinstance(data, bytes) else data.encode()
85
+ return SHA256.new(data_).hexdigest()
86
+
87
+
modules/settings.py CHANGED
@@ -22,7 +22,7 @@ USERS = json.loads(str(os.getenv("USER_KEYS")).replace("\n", ""))
22
 
23
  JWT_SECRET = USERS["master"]
24
  JWT_ALGORITHM = "HS256"
25
- JWT_EXPIRATION_TIME_MINUTES_API = 30
26
  JWT_EXPIRATION_TIME_MINUTES_VIEW = 7*24*60
27
 
28
 
 
22
 
23
  JWT_SECRET = USERS["master"]
24
  JWT_ALGORITHM = "HS256"
25
+ JWT_EXPIRATION_TIME_MINUTES_API = 7*24*60
26
  JWT_EXPIRATION_TIME_MINUTES_VIEW = 7*24*60
27
 
28
 
static/js/chatHandler.js CHANGED
@@ -53,8 +53,7 @@ class ChatGPT{
53
  let data = {
54
  fingerprint: this.fp,
55
  public_key: this.pubk,
56
- signature: this.tokenSigned,
57
- data: this.token
58
  }
59
 
60
  $.ajax({
 
53
  let data = {
54
  fingerprint: this.fp,
55
  public_key: this.pubk,
56
+ signature: this.tokenSigned
 
57
  }
58
 
59
  $.ajax({
templates/main.html CHANGED
@@ -173,7 +173,7 @@
173
  </dialog>
174
 
175
  <script>
176
- async function generatex(){
177
  let keypair = await window.crypto.subtle.generateKey(
178
  {
179
  name: "RSA-PSS",
@@ -202,7 +202,7 @@
202
 
203
  let exportKey_u8 = new Uint8Array(exportKey_ba);
204
  let exportKey = "-----BEGIN PUBLIC KEY-----\n"+btoa(String.fromCharCode(...exportKey_u8))+"\n-----END PUBLIC KEY-----"
205
-
206
  console.log("exportedKey");
207
  console.log(exportKey);
208
  console.log("sign");
@@ -217,7 +217,7 @@
217
  cHand = new ChatGPT("{{ token }}", fp.visitorId, exportKey, sign);
218
 
219
  }
220
- generatex()
221
 
222
 
223
 
 
173
  </dialog>
174
 
175
  <script>
176
+ async function start(){
177
  let keypair = await window.crypto.subtle.generateKey(
178
  {
179
  name: "RSA-PSS",
 
202
 
203
  let exportKey_u8 = new Uint8Array(exportKey_ba);
204
  let exportKey = "-----BEGIN PUBLIC KEY-----\n"+btoa(String.fromCharCode(...exportKey_u8))+"\n-----END PUBLIC KEY-----"
205
+
206
  console.log("exportedKey");
207
  console.log(exportKey);
208
  console.log("sign");
 
217
  cHand = new ChatGPT("{{ token }}", fp.visitorId, exportKey, sign);
218
 
219
  }
220
+ start()
221
 
222
 
223