MGLDZM commited on
Commit
537a5db
·
1 Parent(s): 9a8d759
main.py CHANGED
@@ -5,22 +5,23 @@ import os, json
5
  import time
6
  from hashlib import sha256
7
  from modules import model, oauth, security, log_module, llm, chat_functions, settings
8
- from modules.model import User
9
  from fastapi.staticfiles import StaticFiles
10
  from fastapi.templating import Jinja2Templates
11
  from datetime import datetime
 
12
 
 
13
  app = FastAPI(docs_url=None, redoc_url=None)
14
-
15
  app.mount("/static", StaticFiles(directory="static"), name="static")
16
  templates = Jinja2Templates(directory="templates")
17
 
18
  fecha_unix = str(int(time.time()))
19
  log_module.log_write("master", "iniciado", "-")
 
20
 
21
 
22
-
23
- ########################## MAIN ##########################
24
  @app.get("/", response_class=HTMLResponse)
25
  async def main_page(request: Request, user: User = Depends(User.find_from_cookie)):
26
  if not user.can_use("chat"):
@@ -35,80 +36,75 @@ async def main_page(request: Request, user: User = Depends(User.find_from_cookie
35
  "tools": tools
36
  })
37
  return response
 
38
 
39
 
40
- ########################## SECURITY ##########################
 
41
  @app.get("/login", response_class=HTMLResponse)
42
  async def login(request: Request):
43
- ret = templates.TemplateResponse("iniciar_sesion.html", {"request": request, "redirecturi": settings.OAUTH_REDIRECT})
 
44
  ret.delete_cookie("token")
45
  return ret
46
 
47
  @app.get("/oauth", response_class=HTMLResponse)
48
  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)
 
 
52
  user:User = model.User.find_or_create(google_userinfo, fingerprint)
 
 
53
  response = RedirectResponse(url='/')
54
  token = user.create_cookie()
55
  security.set_cookie(response, key="token", value=token)
 
 
56
  user.update_user()
 
 
57
  return response
 
58
 
59
 
60
- ########################## API ##########################
 
 
 
61
 
62
  @app.post("/getConfigs")
63
- async def get_configs(request: Request, data:dict, user: User = Depends(User.find_from_cookie)): #= 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())
67
- tokens = {"mes": mes, "total": sum([z for x in tokens.values() for y in x.values() for z in y.values()])}
68
-
69
- return user.configs.model_dump() | {"tokens": tokens}
 
70
 
71
  @app.post("/setConfigs")
72
- async def set_configs(request: Request, configs: model.Configs, user: User = Depends(User.find_from_cookie)):
73
- user.configs = configs
74
  user.update_user()
75
-
76
-
77
- return {"success":True}
78
 
79
 
80
  @app.post("/getToken")
81
- async def get_token(request: Request, response: Response, user: User = Depends(User.find_from_data)):
82
-
83
- if user._session.hashed != security.sha256(user.gid + user._guid + data["fingerprint"]):
84
- security.raise_401()
85
-
86
-
87
- user._session = model.Session(**{
88
- "gid": user.gid,
89
- "guid": user._guid,
90
- "fprint": data["fingerprint"],
91
- "public_key": data["public_key"]
92
- })
93
-
94
- security.challenges[data["fingerprint"]] = user._session.challenge
95
- security.set_cookie(response, "token", user.create_session_cookie(), {"hours": 24})
96
- return {"success":True, "challenge": user._session.challenge}
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
- # data: model.Session = Depends(model.Session.validate)
106
- # user.session = model.Session(**data)
107
- # data.pop("fingerprint")
108
- # security.validate_signature(**data)
109
- # security.create_jwt_token(data)
110
- # token = user.create_token()
111
- pass
112
 
113
 
114
  @app.post("/chat")
 
5
  import time
6
  from hashlib import sha256
7
  from modules import model, oauth, security, log_module, llm, chat_functions, settings
8
+ from modules.model import User, Session
9
  from fastapi.staticfiles import StaticFiles
10
  from fastapi.templating import Jinja2Templates
11
  from datetime import datetime
12
+ from typing import Annotated
13
 
14
+ ####################### APP SETUP ########################
15
  app = FastAPI(docs_url=None, redoc_url=None)
 
16
  app.mount("/static", StaticFiles(directory="static"), name="static")
17
  templates = Jinja2Templates(directory="templates")
18
 
19
  fecha_unix = str(int(time.time()))
20
  log_module.log_write("master", "iniciado", "-")
21
+ ##########################################################
22
 
23
 
24
+ ######################## ROUTE: / ########################
 
25
  @app.get("/", response_class=HTMLResponse)
26
  async def main_page(request: Request, user: User = Depends(User.find_from_cookie)):
27
  if not user.can_use("chat"):
 
36
  "tools": tools
37
  })
38
  return response
39
+ ##########################################################
40
 
41
 
42
+
43
+ #################### SECURITY (OAUTH) ####################
44
  @app.get("/login", response_class=HTMLResponse)
45
  async def login(request: Request):
46
+ # Shows the Start "session with google" button, and deletes token cookie
47
+ ret = templates.TemplateResponse("login.html", {"request": request, "redirecturi": settings.OAUTH_REDIRECT})
48
  ret.delete_cookie("token")
49
  return ret
50
 
51
  @app.get("/oauth", response_class=HTMLResponse)
52
  async def validate_oauth(request: Request):
53
+ # Get the oauth get params,
54
+ # look for the google validation and info,
55
+ # set the session cookie and save user in DB
56
+
57
+ # Extract the Get params
58
  params = dict(request.query_params)
59
+
60
+ # Client browser fingerprint bypassed with google oauth as "state"
61
  fingerprint = params["state"].split("=",1)[1]
62
+
63
+ # Get the google user info
64
  google_userinfo = oauth.validate_redirect(params)
65
+
66
+ # Create the user model with the google info
67
  user:User = model.User.find_or_create(google_userinfo, fingerprint)
68
+
69
+ # Prepare redirect response and set the session cookie
70
  response = RedirectResponse(url='/')
71
  token = user.create_cookie()
72
  security.set_cookie(response, key="token", value=token)
73
+
74
+ # Saves the user
75
  user.update_user()
76
+
77
+
78
  return response
79
+ ##########################################################
80
 
81
 
82
+ ########################## APIs ##########################
83
+
84
+ User_find_from_data = Annotated[User, Depends(User.find_from_data)]
85
+ Session_find_from_data = Annotated[Session, Depends(Session.find_from_data)]
86
 
87
  @app.post("/getConfigs")
88
+ async def get_configs(response: Response, user: User_find_from_data):
 
89
  year, month = datetime.now().strftime("%y-%m").split("-")
90
+ month = sum(user.tokens.get(year, {}).get(month, {"_":0}).values())
91
+ total = sum([z for x in user.tokens.values() for y in x.values() for z in y.values()])
92
+ tokens = {"month": month, "total": total}
93
+ user._session.create_cookie(response)
94
+ return user.configs.model_dump() | {"tokens": tokens, "challenge": user._session.challenge}
95
 
96
  @app.post("/setConfigs")
97
+ async def set_configs(response: Response, user: User_find_from_data):
98
+ user.configs = user._data
99
  user.update_user()
100
+ user._session.create_cookie(response)
101
+ return {"success":True, "challenge": user._session.challenge}
 
102
 
103
 
104
  @app.post("/getToken")
105
+ async def get_token(response: Response, session: Session_find_from_data):
106
+ session.create_cookie(response)
107
+ return {"success":True, "challenge": session.challenge}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
 
110
  @app.post("/chat")
modules/model.py CHANGED
@@ -1,7 +1,7 @@
1
- import os
2
  from pymongo.mongo_client import MongoClient
3
  from pymongo.server_api import ServerApi
4
- from fastapi import Request
5
  from . import log_module, security, settings
6
  from datetime import timezone, datetime, timedelta
7
  from pydantic import BaseModel, Field, PrivateAttr
@@ -59,6 +59,7 @@ class Chat(BaseModel):
59
 
60
  def new_msg(self: Self):
61
  return Message(role="", content="")
 
62
  class Session(BaseModel):
63
  gid: str
64
  fprint: str
@@ -66,7 +67,8 @@ class Session(BaseModel):
66
  guid: str
67
  public_key: str = ""
68
  challenge: str = str(uuid.uuid4())
69
-
 
70
  def __init__(self, **kwargs):
71
  kwargs["guid"] = kwargs.get("guid", str(uuid.uuid4()))
72
  kwargs["hashed"] = security.sha256(kwargs["guid"] + kwargs["fprint"])
@@ -79,16 +81,50 @@ class Session(BaseModel):
79
  security.raise_401()
80
  return True
81
 
82
- def create_token(self: Self):
83
- self.challenge = str(uuid.uuid4())
84
- dump = self.model_dump()
85
- dump["fprint"] = security.sha256(
86
- dump["fprint"]+
87
- dump.pop("challenge")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  )
89
 
90
- return security.create_jwt_token(dump)
 
 
 
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  class User(BaseModel):
94
  name: str
@@ -106,49 +142,55 @@ class User(BaseModel):
106
 
107
  @classmethod
108
  def find_or_create(cls: Self, data: dict, fprint: str)-> Self:
109
- if ( found_:= DB.user.find_one({"gid":data["gid"]}) ):
110
- found:Self = cls(**found_)
111
- found._session = Session(gid=found.gid, fprint=fprint)
112
- log_module.logger.info(f"User logged in: {found.gid} | fp: {found._session.fprint}")
113
- return found
114
 
115
- user:Self = cls(**data)
116
- DB.user.insert_one(user.model_dump())
 
 
 
 
 
117
  user._session = Session(gid=user.gid, fprint=fprint)
118
- log_module.logger.info(f"User created: {user.gid} | fp: {user._session.fprint}")
119
- return user
120
 
121
- def create_session(self: Self) -> str:
122
- return Session({
123
- "gid": self.gid,
124
- "fprint":self._session.fprint,
125
- "guid": self._session.guid
126
- })
127
-
128
  @classmethod
129
  def find_from_cookie(cls:Self, request: Request) -> Self:
130
- data:dict = security.token_from_cookie(request)
131
- found:dict = DB.user.find_one({"gid":data["gid"]})
132
-
133
- if found and data.get("gid", None) and data.get("guid", None):
134
- user: Self = cls(**found)
135
- user._session = Session(gid = data["gid"], guid = data["guid"], fprint = data["fprint"])
136
- return user
137
 
138
- security.raise_307()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  @classmethod
141
  def find_from_data(cls:Self, request: Request, data:dict) -> Self:
142
-
143
- cookie_data:dict = security.token_from_cookie(request)
144
- found:dict = DB.user.find_one({"gid":data["gid"]})
145
 
146
- if found and data.get("gid", None) and data.get("guid", None):
147
- user: Self = cls(**found)
148
- user._session = Session(gid = data["gid"], guid = data["guid"], fprint = data["fprint"])
149
- return user
150
-
151
- security.raise_307()
 
 
 
 
152
  def update_description(self: Self, message: str) -> None:
153
  log_module.logger.info(f"Description Updated: {self.gid}")
154
  DB.user.update_one(
@@ -161,6 +203,7 @@ class User(BaseModel):
161
  return security.can_use(self.role, activity)
162
 
163
  def update_user(self: Self, message: Message = None) -> None:
 
164
  DB.user.update_one({"gid": self.gid}, {"$set": self.model_dump()})
165
 
166
  @staticmethod
@@ -173,14 +216,7 @@ class User(BaseModel):
173
  return security.create_jwt_token({
174
  "gid":self.gid,
175
  "guid": self._session.guid,
176
- "fprint": security.sha256(self.gid + self._session.guid + self._session.fprint) })
177
 
178
- def create_session_cookie(self:Self):
179
- return security.create_jwt_token({
180
- "gid":self.gid,
181
- "guid": self._guid,
182
- "fprint": security.sha256(self.gid + self._session.guid + self._session.fprint),
183
- "public_key": self._session.public_key,
184
- "challenge": self._session.challenge
185
- })
186
 
 
1
+ import os, json
2
  from pymongo.mongo_client import MongoClient
3
  from pymongo.server_api import ServerApi
4
+ from fastapi import Request, Response
5
  from . import log_module, security, settings
6
  from datetime import timezone, datetime, timedelta
7
  from pydantic import BaseModel, Field, PrivateAttr
 
59
 
60
  def new_msg(self: Self):
61
  return Message(role="", content="")
62
+
63
  class Session(BaseModel):
64
  gid: str
65
  fprint: str
 
67
  guid: str
68
  public_key: str = ""
69
  challenge: str = str(uuid.uuid4())
70
+ data: dict = {}
71
+
72
  def __init__(self, **kwargs):
73
  kwargs["guid"] = kwargs.get("guid", str(uuid.uuid4()))
74
  kwargs["hashed"] = security.sha256(kwargs["guid"] + kwargs["fprint"])
 
81
  security.raise_401()
82
  return True
83
 
84
+ @classmethod
85
+ def find_from_data(cls:Self, request: Request, data:dict) -> Self:
86
+ cookie_data:dict = security.token_from_cookie(request)
87
+
88
+ if "gid" not in cookie_data or "guid" not in cookie_data:
89
+ log_module.logger.error("Cookie without session needed data")
90
+ security.raise_401()
91
+
92
+ if not (public_key := cookie_data.get("public_key", None)): # FIX Vuln Code
93
+ if request.scope["path"] != "/getToken":
94
+ log_module.logger.error(f"User without public key saved | {json.dumps(cookie_data)}")
95
+ security.raise_401()
96
+ else:
97
+ log_module.logger.info(f"API public key set gor user {cookie_data['gid']}")
98
+ public_key = data["public_key"]
99
+ else:
100
+ security.check_challenge(data["fingerprint"], cookie_data["challenge"])
101
+
102
+ session: Self = cls(
103
+ gid = cookie_data["gid"],
104
+ fprint = data["fingerprint"],
105
+ guid = cookie_data["guid"],
106
+ public_key = public_key,
107
+ data = data
108
  )
109
 
110
+ if session.hashed != cookie_data["fprint"]:
111
+ log_module.logger.error(f"Fingerprint didnt match | {json.dumps(cookie_data)}")
112
+ security.raise_401()
113
+
114
 
115
+ security.add_challenge(session.fprint, session.challenge)
116
+
117
+ return session
118
+
119
+ def create_cookie(self:Self, response: Response):
120
+ jwt = security.create_jwt_token({
121
+ "gid":self.gid,
122
+ "guid": self.guid,
123
+ "fprint": self.hashed,
124
+ "public_key": self.public_key,
125
+ "challenge": self.challenge
126
+ })
127
+ security.set_cookie(response, "token", jwt, {"hours": 24})
128
 
129
  class User(BaseModel):
130
  name: str
 
142
 
143
  @classmethod
144
  def find_or_create(cls: Self, data: dict, fprint: str)-> Self:
 
 
 
 
 
145
 
146
+ found = DB.user.find_one({"gid":data["gid"]})
147
+
148
+ user:Self = cls(**found) if found else cls(**data)
149
+
150
+ if not found:
151
+ DB.user.insert_one(user.model_dump())
152
+
153
  user._session = Session(gid=user.gid, fprint=fprint)
 
 
154
 
155
+ log_module.logger.info(f"User {'logged' if found else'created'}: {user.gid} | fp: {user._session.fprint}")
156
+ return user
157
+
 
 
 
 
158
  @classmethod
159
  def find_from_cookie(cls:Self, request: Request) -> Self:
160
+ cookie_data:dict = security.token_from_cookie(request)
 
 
 
 
 
 
161
 
162
+ if "gid" not in cookie_data or "guid" not in cookie_data:
163
+ log_module.logger.error("Cookie without needed data")
164
+ security.raise_307()
165
+
166
+ found:dict = DB.user.find_one({"gid":cookie_data["gid"]})
167
+
168
+ if not found:
169
+ log_module.logger.error("User not found on DB")
170
+ security.raise_307()
171
+
172
+ user: Self = cls(**found)
173
+ user._session = Session(
174
+ gid = cookie_data["gid"],
175
+ guid = cookie_data["guid"],
176
+ fprint = cookie_data["fprint"]
177
+ )
178
+ return user
179
 
180
  @classmethod
181
  def find_from_data(cls:Self, request: Request, data:dict) -> Self:
182
+ session = Session.find_from_data(request, data)
 
 
183
 
184
+ found:dict = DB.user.find_one({"gid":session.gid})
185
+
186
+ if not found:
187
+ log_module.logger.error("User not found on DB")
188
+ security.raise_307()
189
+
190
+ user: Self = cls(**found)
191
+ user._session = session
192
+ return user
193
+
194
  def update_description(self: Self, message: str) -> None:
195
  log_module.logger.info(f"Description Updated: {self.gid}")
196
  DB.user.update_one(
 
203
  return security.can_use(self.role, activity)
204
 
205
  def update_user(self: Self, message: Message = None) -> None:
206
+ log_module.logger.info(f"User Updated: {self.gid}")
207
  DB.user.update_one({"gid": self.gid}, {"$set": self.model_dump()})
208
 
209
  @staticmethod
 
216
  return security.create_jwt_token({
217
  "gid":self.gid,
218
  "guid": self._session.guid,
219
+ "fprint": self._session.hashed})
220
 
221
+
 
 
 
 
 
 
 
222
 
modules/security.py CHANGED
@@ -20,7 +20,13 @@ for key in settings.USERS:
20
  settings.USERS[key] = sha256(password.encode('UTF-8')).hexdigest()
21
 
22
 
 
 
23
 
 
 
 
 
24
 
25
  def create_jwt_token(data, maxlife=settings.JWT_EXPIRATION_TIME_MINUTES_API):
26
  expire = datetime.utcnow() + timedelta(minutes=maxlife)
 
20
  settings.USERS[key] = sha256(password.encode('UTF-8')).hexdigest()
21
 
22
 
23
+ def add_challenge(fprint:str, challenge:str):
24
+ challenges[fprint] = challenge
25
 
26
+ def check_challenge(fprint:str, challenge:str):
27
+ if challenges.pop(fprint, None) == challenge:
28
+ return True
29
+ raise_401()
30
 
31
  def create_jwt_token(data, maxlife=settings.JWT_EXPIRATION_TIME_MINUTES_API):
32
  expire = datetime.utcnow() + timedelta(minutes=maxlife)
static/js/chatHandler.js CHANGED
@@ -44,13 +44,7 @@ class ChatGPT{
44
 
45
  }
46
 
47
- obtenerToken(challenge){
48
- let data = {
49
- fingerprint: this.secHand.fingerprint,
50
- public_key: this.secHand.publicKey,
51
- operation: "challenge"
52
- }
53
-
54
  $.ajax({
55
  method: "POST",
56
  url: "/getToken",
@@ -58,7 +52,10 @@ class ChatGPT{
58
  "Autorization": "Bearer " + this.token,
59
  'Content-Type': 'application/json',
60
  },
61
- data: JSON.stringify(data),
 
 
 
62
  timeout: 5000,
63
  dataType: "json"
64
  }).done((data) => {
@@ -84,7 +81,8 @@ class ChatGPT{
84
  'Content-Type': 'application/json',
85
  },
86
  data:JSON.stringify({
87
- "challenge": this.challenge
 
88
  }),
89
  timeout: 5000,
90
  dataType: "json"
@@ -114,7 +112,7 @@ class ChatGPT{
114
  $(".range select").on("change", function(){
115
  self.config[this.id] = this.value
116
  })
117
- $("#tokensUsedMonth").text(data.tokens.mes)
118
  $("#tokensUsedTotal").text(data.tokens.total)
119
 
120
 
@@ -171,9 +169,16 @@ class ChatGPT{
171
  "Autorization": "Bearer " + this.token,
172
  'Content-Type': 'application/json',
173
  },
174
- data: JSON.stringify(this.config),
 
 
 
 
175
  timeout: 5000,
176
  dataType: "json"
 
 
 
177
  })
178
  }
179
 
 
44
 
45
  }
46
 
47
+ obtenerToken(){
 
 
 
 
 
 
48
  $.ajax({
49
  method: "POST",
50
  url: "/getToken",
 
52
  "Autorization": "Bearer " + this.token,
53
  'Content-Type': 'application/json',
54
  },
55
+ data: JSON.stringify({
56
+ fingerprint: this.secHand.fingerprint,
57
+ public_key: this.secHand.publicKey
58
+ }),
59
  timeout: 5000,
60
  dataType: "json"
61
  }).done((data) => {
 
81
  'Content-Type': 'application/json',
82
  },
83
  data:JSON.stringify({
84
+ challenge: this.challenge,
85
+ fingerprint: this.secHand.fingerprint
86
  }),
87
  timeout: 5000,
88
  dataType: "json"
 
112
  $(".range select").on("change", function(){
113
  self.config[this.id] = this.value
114
  })
115
+ $("#tokensUsedMonth").text(data.tokens.month)
116
  $("#tokensUsedTotal").text(data.tokens.total)
117
 
118
 
 
169
  "Autorization": "Bearer " + this.token,
170
  'Content-Type': 'application/json',
171
  },
172
+ data: JSON.stringify(
173
+ {...this.config,
174
+ fingerprint: this.secHand.fingerprint,
175
+ challenge: this.challenge,
176
+ }),
177
  timeout: 5000,
178
  dataType: "json"
179
+ }).done((data) => {
180
+ this.secHand.sign(data.challenge).then((result) => {this.challenge = result})
181
+ this.token = data.token
182
  })
183
  }
184
 
templates/{iniciar_sesion.html → login.html} RENAMED
File without changes