asFrants commited on
Commit
d0f5098
1 Parent(s): cdd5b2f

finalized auth

Browse files
app.py CHANGED
@@ -111,15 +111,11 @@ class Summarizer:
111
 
112
  def get_pipe(self, lang: str):
113
  logger.info(f"Pipe language: {lang}")
114
- summary = {
115
- "en": self.en_summary_pipe,
116
- "ru": self.ru_summary_pipe,
117
- }
118
- sentiment = {
119
- "en": self.en_sentiment_pipe,
120
- "ru": self.ru_sentiment_pipe,
121
- }
122
- return summary[lang], sentiment[lang]
123
 
124
  def summarize(self, req: TextRequest, lang: str = "en") -> Result:
125
  sum_pipe, sent_pipe = self.get_pipe(lang)
@@ -134,7 +130,7 @@ class Summarizer:
134
  )
135
  return result
136
 
137
- def summ(self, req: TextRequest, lang: str = "en") -> str:
138
  return self.summarize(req, lang).to_str()
139
 
140
 
@@ -185,12 +181,12 @@ if __name__ == "__main__":
185
  ru_inbtn = gr.Button("Запустить")
186
 
187
  en_inbtn.click(
188
- pipe.summ,
189
  [en_inputs, en_lang],
190
  [en_outputs],
191
  )
192
  ru_inbtn.click(
193
- pipe.summ,
194
  [ru_inputs, ru_lang],
195
  [ru_outputs],
196
  )
 
111
 
112
  def get_pipe(self, lang: str):
113
  logger.info(f"Pipe language: {lang}")
114
+ if lang == "en":
115
+ return self.en_summary_pipe, self.en_sentiment_pipe
116
+ if lang == "ru":
117
+ return self.ru_summary_pipe, self.ru_sentiment_pipe
118
+ raise ValueError(f"Language {lang} is not supported")
 
 
 
 
119
 
120
  def summarize(self, req: TextRequest, lang: str = "en") -> Result:
121
  sum_pipe, sent_pipe = self.get_pipe(lang)
 
130
  )
131
  return result
132
 
133
+ def get_summary(self, req: TextRequest, lang: str = "en") -> str:
134
  return self.summarize(req, lang).to_str()
135
 
136
 
 
181
  ru_inbtn = gr.Button("Запустить")
182
 
183
  en_inbtn.click(
184
+ pipe.get_summary,
185
  [en_inputs, en_lang],
186
  [en_outputs],
187
  )
188
  ru_inbtn.click(
189
+ pipe.get_summary,
190
  [ru_inputs, ru_lang],
191
  [ru_outputs],
192
  )
main.py CHANGED
@@ -1,46 +1,62 @@
1
- import os
2
- import gradio as gr
3
  import random
4
- import secrets
5
- from typing import Annotated
6
- from fastapi import FastAPI, Request, status, Depends
 
7
  from fastapi.staticfiles import StaticFiles
8
  from fastapi.responses import HTMLResponse, RedirectResponse
9
  from fastapi.templating import Jinja2Templates
10
- from fastapi.exceptions import HTTPException
11
- from fastapi.security import HTTPBasic, HTTPBasicCredentials
12
  from loguru import logger
13
- from dotenv import load_dotenv
14
-
15
- from app import Summarizer, TextRequest, Result
16
  from app import (
17
  EN_SENTIMENT_MODEL,
18
  EN_SUMMARY_MODEL,
19
- RU_SENTIMENT_MODEL,
20
- RU_SUMMARY_MODEL,
21
  )
22
- from app import DEFAULT_EN_TEXT, DEFAULT_RU_TEXT
23
  from models.forms import VerificationForm
 
 
24
 
25
- load_dotenv()
26
-
27
- SITE_KEY = os.getenv("SITE_KEY")
28
- API_USER = os.getenv("API_USER")
29
- API_PWD = os.getenv("API_PWD")
30
- users = set()
31
 
32
  app = FastAPI()
33
  pipe = Summarizer()
34
- security = HTTPBasic()
35
 
36
- # mount FastAPI StaticFiles server
37
  app.mount("/static", StaticFiles(directory="static"), name="static")
38
  templates = Jinja2Templates(directory="templates")
39
 
40
-
41
- @app.get("/")
42
- async def index(request: Request):
43
- return RedirectResponse("/index/", status_code=302)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
 
46
  @app.get("/verify_page", response_class=HTMLResponse)
@@ -49,143 +65,71 @@ async def verify_page(request: Request):
49
  return templates.TemplateResponse(
50
  request=request,
51
  name="verification.html",
52
- context={"site_key": SITE_KEY, "captcha_id": captcha_id},
53
  )
54
 
55
 
56
  @app.post("/verify")
57
- async def verify(request: Request):
58
  form = VerificationForm(request)
59
  await form.load_data()
60
  if await form.is_valid():
61
  logger.info("Form is valid")
62
- return RedirectResponse("/index/", status_code=302)
 
 
 
 
 
 
 
63
  return await verify_page(request)
64
 
65
 
66
- def get_current_username(
67
- credentials: Annotated[HTTPBasicCredentials, Depends(security)]
68
- ):
69
- current_username_bytes = credentials.username.encode("utf8")
70
- correct_username_bytes = bytes(API_USER, "utf-8")
71
- is_correct_username = secrets.compare_digest(
72
- current_username_bytes, correct_username_bytes
73
- )
74
- current_password_bytes = credentials.password.encode("utf8")
75
- correct_password_bytes = bytes(API_PWD, "utf-8")
76
- is_correct_password = secrets.compare_digest(
77
- current_password_bytes, correct_password_bytes
78
- )
79
- if not (is_correct_username and is_correct_password):
80
- raise HTTPException(
81
- status_code=status.HTTP_401_UNAUTHORIZED,
82
- detail="Incorrect username or password",
83
- headers={"WWW-Authenticate": "Basic"},
84
- )
85
- return credentials.username
86
-
87
-
88
- @app.post("/summ_ru", response_model=Result)
89
- async def ru_summ_api(
90
- request: TextRequest, username: Annotated[str, Depends(get_current_username)]
91
- ):
92
- results = pipe.summarize(request.text, lang="ru")
93
- logger.info(results)
94
- return results
95
-
96
-
97
- @app.post("/summ_en", response_model=Result)
98
- async def en_summ_api(
99
- request: TextRequest, username: Annotated[str, Depends(get_current_username)]
100
- ):
101
- results = pipe.summarize(request.text, lang="en")
102
- logger.info(results)
103
- return results
104
-
105
-
106
- @app.exception_handler(403)
107
- async def unavailable_error(request: Request, exc: HTTPException):
108
- logger.warning("Error 403")
109
- return templates.TemplateResponse(
110
- "errors/error.html",
111
- {"request": request, "message": "403. Sorry, this page unavailable."},
112
- status_code=403,
113
- )
114
-
115
 
116
- @app.exception_handler(404)
117
- async def not_found_error(request: Request, exc: HTTPException):
118
- logger.warning("Error 404")
119
- return templates.TemplateResponse(
120
- "errors/error.html",
121
- {"request": request, "message": "404. Page Not Found."},
122
- status_code=404,
123
- )
124
 
125
-
126
- @app.exception_handler(500)
127
- async def internal_error(request: Request, exc: HTTPException):
128
- logger.warning("Error 500")
129
- return templates.TemplateResponse(
130
- "errors/error.html",
131
- {"request": request, "message": "500. Oops. Something has gone wrong."},
132
- status_code=500,
133
- )
134
 
135
 
136
  with gr.Blocks() as demo:
137
- with gr.Row():
138
- with gr.Column(scale=2, min_width=600):
139
- en_sum_description = gr.Markdown(
140
- value=f"Model for Summary: {EN_SUMMARY_MODEL}"
141
- )
142
- en_sent_description = gr.Markdown(
143
- value=f"Model for Sentiment: {EN_SENTIMENT_MODEL}"
144
- )
145
- en_inputs = gr.Textbox(
146
- label="en_input",
147
- lines=5,
148
- value=DEFAULT_EN_TEXT,
149
- placeholder=DEFAULT_EN_TEXT,
150
- )
151
- en_lang = gr.Textbox(value="en", visible=False)
152
- en_outputs = gr.Textbox(
153
- label="en_output",
154
- lines=5,
155
- placeholder="Summary and Sentiment would be here...",
156
- )
157
- en_inbtn = gr.Button("Proceed")
158
- with gr.Column(scale=2, min_width=600):
159
- ru_sum_description = gr.Markdown(
160
- value=f"Model for Summary: {RU_SUMMARY_MODEL}"
161
- )
162
- ru_sent_description = gr.Markdown(
163
- value=f"Model for Sentiment: {RU_SENTIMENT_MODEL}"
164
- )
165
- ru_inputs = gr.Textbox(
166
- label="ru_input",
167
- lines=5,
168
- value=DEFAULT_RU_TEXT,
169
- placeholder=DEFAULT_RU_TEXT,
170
- )
171
- ru_lang = gr.Textbox(value="ru", visible=False)
172
- ru_outputs = gr.Textbox(
173
- label="ru_output",
174
- lines=5,
175
- placeholder="Здесь будет обобщение и эмоциональный окрас текста...",
176
- )
177
- ru_inbtn = gr.Button("Запустить")
178
-
179
- en_inbtn.click(
180
- pipe.summ,
181
  [en_inputs, en_lang],
182
  [en_outputs],
183
  )
184
- ru_inbtn.click(
185
- pipe.summ,
186
- [ru_inputs, ru_lang],
187
- [ru_outputs],
188
- )
189
 
 
190
  # mounting at the root path
191
  app = gr.mount_gradio_app(app, demo, path="/index")
 
 
 
1
  import random
2
+ import gradio as gr
3
+
4
+ # from fastapi.security import APIKeyHeader
5
+ from fastapi import FastAPI, Request, Depends
6
  from fastapi.staticfiles import StaticFiles
7
  from fastapi.responses import HTMLResponse, RedirectResponse
8
  from fastapi.templating import Jinja2Templates
 
 
9
  from loguru import logger
10
+ from app import Summarizer, TextRequest
 
 
11
  from app import (
12
  EN_SENTIMENT_MODEL,
13
  EN_SUMMARY_MODEL,
 
 
14
  )
15
+ from app import DEFAULT_EN_TEXT
16
  from models.forms import VerificationForm
17
+ from models.exceptions import MyHTTPException, my_http_exception_handler
18
+ from models.security import AuthUsers
19
 
 
 
 
 
 
 
20
 
21
  app = FastAPI()
22
  pipe = Summarizer()
23
+ users = AuthUsers()
24
 
 
25
  app.mount("/static", StaticFiles(directory="static"), name="static")
26
  templates = Jinja2Templates(directory="templates")
27
 
28
+ # auth_header = APIKeyHeader(name="Authorization", auto_error=True)
29
+ # router = APIRouter(
30
+ # prefix="/v1",
31
+ # tags=["API v1"],
32
+ # dependencies=[Security(auth_header)],
33
+ # )
34
+ # app.include_router(router)
35
+
36
+ app.add_exception_handler(MyHTTPException, my_http_exception_handler)
37
+
38
+
39
+ @app.middleware("http")
40
+ async def add_process_time_header(request: Request, call_next):
41
+ response = await call_next(request)
42
+ token = request.cookies.get("Authorization")
43
+ dest = request.url.path.split("/")[-1]
44
+ resp_headers = dict(response.headers)
45
+ cont_type = resp_headers.get("content-type")
46
+ if (
47
+ not cont_type
48
+ or "text/html" not in cont_type
49
+ or dest
50
+ in [
51
+ "docs",
52
+ "verify_page",
53
+ "verify",
54
+ ]
55
+ ):
56
+ return response
57
+ if not token or token == "":
58
+ return RedirectResponse("/verify_page", status_code=307)
59
+ return response
60
 
61
 
62
  @app.get("/verify_page", response_class=HTMLResponse)
 
65
  return templates.TemplateResponse(
66
  request=request,
67
  name="verification.html",
68
+ context={"captcha_id": captcha_id},
69
  )
70
 
71
 
72
  @app.post("/verify")
73
+ async def verify(request: Request, checked_user: bool = Depends(users.get_cookie_data)):
74
  form = VerificationForm(request)
75
  await form.load_data()
76
  if await form.is_valid():
77
  logger.info("Form is valid")
78
+ response = RedirectResponse("/index", status_code=302)
79
+ if not checked_user:
80
+ user_token = users.generate_user_token()
81
+ users.add_user_session(user_token)
82
+ response.set_cookie(key="Authorization", value=user_token)
83
+ logger.info(f"Issued token: {user_token}")
84
+ return response
85
+ logger.info("Validation error")
86
  return await verify_page(request)
87
 
88
 
89
+ @app.get("/")
90
+ async def get_main_page():
91
+ return RedirectResponse("/index", status_code=302)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
 
 
 
 
 
 
 
 
93
 
94
+ def get_summary(text: TextRequest, lang: str, request: gr.Request):
95
+ token = request.cookies["Authorization"]
96
+ if users.verify_user(token):
97
+ users.add_user_session(token)
98
+ return pipe.get_summary(text, lang)
99
+ logger.info("User not verified")
100
+ return "Sorry. You are not verified."
 
 
101
 
102
 
103
  with gr.Blocks() as demo:
104
+ with gr.Column(scale=2, min_width=600):
105
+ en_sum_description = gr.Markdown(value=f"Model for Summary: {EN_SUMMARY_MODEL}")
106
+ en_sent_description = gr.Markdown(
107
+ value=f"Model for Sentiment: {EN_SENTIMENT_MODEL}"
108
+ )
109
+ verify_href = gr.Markdown(
110
+ value="Available only for verified users.[Click here for verification.](/verify_page)"
111
+ )
112
+ en_inputs = gr.Textbox(
113
+ label="Input",
114
+ lines=5,
115
+ value=DEFAULT_EN_TEXT,
116
+ placeholder=DEFAULT_EN_TEXT,
117
+ )
118
+ # en_lang = gr.Textbox(value="en", visible=False)
119
+ en_lang = gr.Radio(["en", "ru"], value="en", label="Language")
120
+ en_outputs = gr.Textbox(
121
+ label="Output",
122
+ lines=5,
123
+ placeholder="Summary and Sentiment would be here...",
124
+ )
125
+ en_inbtn = gr.Button("Proceed")
126
+
127
+ en_inbtn = en_inbtn.click(
128
+ get_summary,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  [en_inputs, en_lang],
130
  [en_outputs],
131
  )
 
 
 
 
 
132
 
133
+ # demo.launch(server_name="127.0.0.1", server_port=8080, share=False)
134
  # mounting at the root path
135
  app = gr.mount_gradio_app(app, demo, path="/index")
models/exceptions.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import Request
2
+ from fastapi.exceptions import HTTPException
3
+ from fastapi.templating import Jinja2Templates
4
+
5
+
6
+ templates = Jinja2Templates(directory="templates")
7
+
8
+
9
+ class MyHTTPException(HTTPException):
10
+ pass
11
+
12
+
13
+ def my_http_exception_handler(request: Request, exc: HTTPException):
14
+ return templates.TemplateResponse(
15
+ "errors/error.html",
16
+ {"request": request, "message": f"{exc.status_code}. {exc.detail}."},
17
+ status_code=exc.status_code,
18
+ )
models/security.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import secrets
3
+ from typing import Annotated
4
+ from fastapi.security import HTTPBasic, HTTPBasicCredentials
5
+ from fastapi import status, Depends, Cookie
6
+ from datetime import datetime, timedelta
7
+ from collections import defaultdict
8
+ from loguru import logger
9
+ from dotenv import load_dotenv
10
+
11
+ from models.exceptions import MyHTTPException
12
+
13
+ load_dotenv()
14
+
15
+ API_USER = os.getenv("API_USER")
16
+ API_PWD = os.getenv("API_PWD")
17
+ API_KEY = os.getenv("API_KEY")
18
+
19
+ security = HTTPBasic()
20
+
21
+
22
+ class AuthUsers:
23
+ REQUESTS_LIMIT = 3 # 3 REQUESTS PER MINUTE
24
+ AUTH_TIME = 10 # 10 MINUTES
25
+ users: set
26
+ users_auth: defaultdict(list)
27
+
28
+ def __init__(self):
29
+ self.users = set()
30
+ self.users_auth = defaultdict(list)
31
+
32
+ def generate_user_token(self) -> str:
33
+ return secrets.token_hex(16)
34
+
35
+ def verify_user(self, user_token: str) -> bool:
36
+ logger.info(f"Check user token: {user_token}")
37
+ if user_token in self.users:
38
+ print(self.users_auth[user_token])
39
+ if len(self.users_auth[user_token]) < self.REQUESTS_LIMIT:
40
+ return True
41
+ elif datetime.now() - self.users_auth[user_token][
42
+ -self.REQUESTS_LIMIT
43
+ ] >= timedelta(minutes=self.AUTH_TIME):
44
+ return True
45
+ else:
46
+ raise MyHTTPException(
47
+ status_code=status.HTTP_429_TOO_MANY_REQUESTS,
48
+ detail="Too many requests. Try again later.",
49
+ )
50
+ logger.info(f"User {user_token} not found")
51
+ raise MyHTTPException(
52
+ status_code=status.HTTP_401_UNAUTHORIZED,
53
+ detail="Unauthorized user",
54
+ )
55
+
56
+ def add_user_session(self, user_token: str):
57
+ if user_token in self.users:
58
+ self.users_auth[user_token].append(datetime.now())
59
+ else:
60
+ self.users.add(user_token)
61
+ self.users_auth.setdefault(user_token, []).append(datetime.now())
62
+ print(self.users_auth[user_token])
63
+ while len(self.users_auth[user_token]) > self.REQUESTS_LIMIT:
64
+ self.users_auth[user_token].pop(0)
65
+ logger.info(f"User's {user_token} session added")
66
+
67
+ def get_cookie_data(
68
+ self, user_token: str = Cookie(default=None, alias="Authorization")
69
+ ) -> list:
70
+ if not user_token or user_token not in self.users:
71
+ logger.info("Unauthorized user")
72
+ return False
73
+ logger.info(f"Verified user with token: {user_token}")
74
+ return True
75
+
76
+
77
+ credentials_exception = MyHTTPException(
78
+ status_code=status.HTTP_401_UNAUTHORIZED,
79
+ detail="Login or password is incorrect",
80
+ headers={"WWW-Authenticate": "Basic"},
81
+ )
82
+
83
+
84
+ def check_api_credentials(
85
+ credentials: Annotated[HTTPBasicCredentials, Depends(security)]
86
+ ):
87
+ current_username_bytes = credentials.username.encode("utf8")
88
+ correct_username_bytes = bytes(API_USER, "utf-8")
89
+ is_correct_username = secrets.compare_digest(
90
+ current_username_bytes, correct_username_bytes
91
+ )
92
+ current_password_bytes = credentials.password.encode("utf8")
93
+ correct_password_bytes = bytes(API_PWD, "utf-8")
94
+ is_correct_password = secrets.compare_digest(
95
+ current_password_bytes, correct_password_bytes
96
+ )
97
+ if not (is_correct_username and is_correct_password):
98
+ raise credentials_exception
99
+ return credentials.username
poetry.lock CHANGED
The diff for this file is too large to render. See raw diff
 
pyproject.toml CHANGED
@@ -10,7 +10,7 @@ readme = "README.md"
10
  python = "^3.10"
11
  transformers = "^4.36.2"
12
  gradio = "^4.11.0"
13
- torch = "1.13.1"
14
  pydantic = "^2.5.3"
15
  pretrainedmodels = "^0.7.4"
16
  sentencepiece = "^0.1.99"
@@ -21,6 +21,8 @@ uvicorn = "^0.27.0"
21
  python-dotenv = "^1.0.1"
22
  jinja2 = "^3.1.3"
23
  aiohttp = "^3.9.3"
 
 
24
 
25
 
26
  [tool.poetry.group.dev.dependencies]
 
10
  python = "^3.10"
11
  transformers = "^4.36.2"
12
  gradio = "^4.11.0"
13
+ torch = "^2.2.0"
14
  pydantic = "^2.5.3"
15
  pretrainedmodels = "^0.7.4"
16
  sentencepiece = "^0.1.99"
 
21
  python-dotenv = "^1.0.1"
22
  jinja2 = "^3.1.3"
23
  aiohttp = "^3.9.3"
24
+ bcrypt = "^4.1.2"
25
+ passlib = "^1.7.4"
26
 
27
 
28
  [tool.poetry.group.dev.dependencies]
templates/bad_request.html DELETED
@@ -1,13 +0,0 @@
1
- {% extends "components/_base.html" %}
2
-
3
- {% block page_content %}
4
- <title> Bad Request </title>
5
- <meta name="description" content="">
6
-
7
- <!-- <h1>Sorry, your request not allowed</h1> -->
8
- <section class="bg-dark text-white pt-4 pb-5">
9
- <div class="justify-content-center">
10
- <h1 class="text-primary">Bad Request</h1>
11
- </div>
12
- </section>
13
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/verification_old.html DELETED
@@ -1,50 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"/>
8
- <link rel="stylesheet" href="{{ url_for('static',path='/css/style.css')}}"/>
9
- <script async src="https://www.google.com/recaptcha/api.js"></script>
10
- <script>
11
- function onSubmit(token) {
12
- document.getElementById("verify_form").submit();
13
- }
14
- </script>
15
- <script>
16
- function onClick() {
17
- grecaptcha.ready(function() {
18
- grecaptcha.execute('{{ site_key }}', {action: 'submit'}).then(function(token) {
19
- // Add your logic to submit to your backend server here.
20
- });
21
- });
22
- }
23
- </script>
24
- <title>Verification Page</title>
25
- </head>
26
- <body>
27
- <div class="row justify-content-center col-md-4">
28
- <div class="align-self-center">
29
- <h1 class="text-primary">Verification Page</h1>
30
- <form id="verify_form" action="{{ url_for('verify') }}" method="post">
31
- <div class="form-group my-3">
32
- <input type="text" class="form-control" name="captcha" placeholder="Type captcha">
33
- </div>
34
- <!-- <button type="submit" class="btn btn-primary">Submit</button> -->
35
-
36
- <!-- reCaptcha v3 -->
37
- <button type="submit" class="g-recaptcha btn btn-primary"
38
- data-sitekey="{{ site_key }}" data-callback="onSubmit"
39
- data-action="submit">Submit
40
- </button>
41
- <!-- reCaptcha v2 -->
42
- <!-- <div class="g-recaptcha" data-sitekey="{{ site_key }}"></div> -->
43
- <!-- <div id="html_element"></div> -->
44
- <!-- <button type="submit" class="btn btn-primary" onclick='onClick()'>Submit</button> -->
45
- </form>
46
- </div>
47
- </div>
48
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"></script>
49
- </body>
50
- </html>