Commit 3586cfaba4678c93c4ec985beb50d7c65937d2ab
1 parent
5b95825d
Exists in
master
and in
1 other branch
fixed async login
Showing
2 changed files
with
79 additions
and
52 deletions
Show diff stats
learnapp.py
1 | 1 | |
2 | 2 | # python standard library |
3 | -from contextlib import contextmanager # `with` statement in db sessions | |
4 | -import logging | |
5 | 3 | from os import path, sys |
4 | +import logging | |
5 | +from contextlib import contextmanager # `with` statement in db sessions | |
6 | +import asyncio | |
6 | 7 | from datetime import datetime |
7 | 8 | |
8 | 9 | # user installed libraries |
... | ... | @@ -20,15 +21,42 @@ from tools import load_yaml |
20 | 21 | # setup logger for this module |
21 | 22 | logger = logging.getLogger(__name__) |
22 | 23 | |
23 | - | |
24 | +# ============================================================================ | |
24 | 25 | class LearnAppException(Exception): |
25 | 26 | pass |
26 | 27 | |
27 | 28 | |
28 | 29 | # ============================================================================ |
30 | +# helper functions | |
31 | +# ============================================================================ | |
32 | +async def check_password(try_pw, password): | |
33 | + try_pw = try_pw.encode('utf-8') | |
34 | + loop = asyncio.get_running_loop() | |
35 | + hashed_pw = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password) | |
36 | + return password == hashed_pw | |
37 | + | |
38 | + | |
39 | +# ============================================================================ | |
29 | 40 | # LearnApp - application logic |
30 | 41 | # ============================================================================ |
31 | 42 | class LearnApp(object): |
43 | + # ------------------------------------------------------------------------ | |
44 | + # helper to manage db sessions using the `with` statement, for example | |
45 | + # with self.db_session() as s: s.query(...) | |
46 | + # ------------------------------------------------------------------------ | |
47 | + @contextmanager | |
48 | + def db_session(self, **kw): | |
49 | + session = self.Session(**kw) | |
50 | + try: | |
51 | + yield session | |
52 | + session.commit() | |
53 | + except: | |
54 | + session.rollback() | |
55 | + logger.error('DB rollback!!!') | |
56 | + finally: | |
57 | + session.close() | |
58 | + | |
59 | + # ------------------------------------------------------------------------ | |
32 | 60 | def __init__(self, config_file): |
33 | 61 | # state of online students |
34 | 62 | self.online = {} |
... | ... | @@ -47,38 +75,44 @@ class LearnApp(object): |
47 | 75 | # ------------------------------------------------------------------------ |
48 | 76 | # login |
49 | 77 | # ------------------------------------------------------------------------ |
50 | - def login(self, uid, pw): | |
78 | + async def login(self, uid, try_pw): | |
79 | + if uid.startswith('l'): # remove prefix 'l' | |
80 | + uid = uid[1:] | |
51 | 81 | |
52 | 82 | with self.db_session() as s: |
53 | - student = s.query(Student).filter(Student.id == uid).one_or_none() | |
54 | - if student is None: | |
83 | + try: | |
84 | + name, password = s.query(Student.name, Student.password).filter_by(id=uid).one() | |
85 | + except: | |
55 | 86 | logger.info(f'User "{uid}" does not exist!') |
56 | - return False # student does not exist | |
57 | - | |
58 | - if bcrypt.checkpw(pw.encode('utf-8'), student.password): | |
59 | - if uid in self.online: | |
60 | - logger.warning(f'User "{uid}" already logged in, overwriting state') | |
61 | - else: | |
62 | - logger.info(f'User "{uid}" logged in') | |
63 | - | |
64 | - tt = s.query(StudentTopic).filter(StudentTopic.student_id == uid) | |
65 | - state = {t.topic_id: | |
66 | - { | |
67 | - 'level': t.level, | |
68 | - 'date': datetime.strptime(t.date, "%Y-%m-%d %H:%M:%S.%f") | |
69 | - } for t in tt} | |
70 | - | |
71 | - self.online[uid] = { | |
72 | - 'number': student.id, | |
73 | - 'name': student.name, | |
74 | - 'state': StudentKnowledge(self.deps, state=state), | |
75 | - # 'learning': None, # current topic learning | |
76 | - } | |
77 | - return True | |
87 | + return False | |
78 | 88 | |
89 | + pw_ok = await check_password(try_pw, password) # async bcrypt | |
90 | + if pw_ok: | |
91 | + if uid in self.online: | |
92 | + logger.warning(f'User "{uid}" already logged in, overwriting state') | |
79 | 93 | else: |
80 | - logger.info(f'User "{uid}" wrong password!') | |
81 | - return False | |
94 | + logger.info(f'User "{uid}" logged in successfully') | |
95 | + | |
96 | + with self.db_session() as s: | |
97 | + tt = s.query(StudentTopic).filter_by(student_id=uid) | |
98 | + | |
99 | + state = {t.topic_id: | |
100 | + { | |
101 | + 'level': t.level, | |
102 | + 'date': datetime.strptime(t.date, "%Y-%m-%d %H:%M:%S.%f") | |
103 | + } for t in tt} | |
104 | + | |
105 | + self.online[uid] = { | |
106 | + 'number': uid, | |
107 | + 'name': name, | |
108 | + 'state': StudentKnowledge(self.deps, state=state), | |
109 | + # 'learning': None, # current topic learning | |
110 | + } | |
111 | + | |
112 | + else: | |
113 | + logger.info(f'User "{uid}" wrong password!') | |
114 | + | |
115 | + return pw_ok | |
82 | 116 | |
83 | 117 | # ------------------------------------------------------------------------ |
84 | 118 | # logout |
... | ... | @@ -206,31 +240,14 @@ class LearnApp(object): |
206 | 240 | n = s.query(Student).count() |
207 | 241 | m = s.query(Topic).count() |
208 | 242 | q = s.query(Answer).count() |
209 | - except Exception: | |
243 | + except Exception as e: | |
210 | 244 | logger.critical(f'Database "{db}" not usable!') |
211 | - sys.exit(1) | |
245 | + raise e | |
212 | 246 | else: |
213 | 247 | logger.info(f'{n:6} students') |
214 | 248 | logger.info(f'{m:6} topics') |
215 | 249 | logger.info(f'{q:6} answers') |
216 | 250 | |
217 | - # ------------------------------------------------------------------------ | |
218 | - # helper to manage db sessions using the `with` statement, for example | |
219 | - # with self.db_session() as s: s.query(...) | |
220 | - # ------------------------------------------------------------------------ | |
221 | - @contextmanager | |
222 | - def db_session(self, **kw): | |
223 | - session = self.Session(**kw) | |
224 | - try: | |
225 | - yield session | |
226 | - session.commit() | |
227 | - except Exception as e: | |
228 | - session.rollback() | |
229 | - raise e | |
230 | - finally: | |
231 | - session.close() | |
232 | - | |
233 | - | |
234 | 251 | |
235 | 252 | # ======================================================================== |
236 | 253 | # methods that do not change state (pure functions) | ... | ... |
serve.py
... | ... | @@ -16,12 +16,23 @@ import functools |
16 | 16 | import tornado.ioloop |
17 | 17 | import tornado.web |
18 | 18 | import tornado.httpserver |
19 | -from tornado import iostream | |
19 | +# from tornado import iostream | |
20 | 20 | |
21 | 21 | # this project |
22 | 22 | from learnapp import LearnApp |
23 | 23 | from tools import load_yaml, md_to_html |
24 | 24 | |
25 | +# ---------------------------------------------------------------------------- | |
26 | +# Decorator used to restrict access to the administrator | |
27 | +# ---------------------------------------------------------------------------- | |
28 | +def admin_only(func): | |
29 | + @functools.wraps(func) | |
30 | + def wrapper(self, *args, **kwargs): | |
31 | + if self.current_user != '0': | |
32 | + raise tornado.web.HTTPError(403) # forbidden | |
33 | + else: | |
34 | + func(self, *args, **kwargs) | |
35 | + return wrapper | |
25 | 36 | |
26 | 37 | # ============================================================================ |
27 | 38 | # WebApplication - Tornado Web Server |
... | ... | @@ -82,8 +93,7 @@ class LoginHandler(BaseHandler): |
82 | 93 | uid = self.get_body_argument('uid') |
83 | 94 | pw = self.get_body_argument('pw') |
84 | 95 | |
85 | - loop = asyncio.get_event_loop() | |
86 | - login_ok = await loop.run_in_executor(None, self.learn.login, uid, pw) | |
96 | + login_ok = await self.learn.login(uid, pw) | |
87 | 97 | |
88 | 98 | if login_ok: |
89 | 99 | self.set_secure_cookie("user", str(uid), expires_days=30) | ... | ... |