Skip to content

Commit 5de6d70

Browse files
authored
Merge pull request #304 from Seluj78/bugfix/jwt
2 parents 2f8ff74 + c327caa commit 5de6d70

File tree

4 files changed

+124
-132
lines changed

4 files changed

+124
-132
lines changed

backend/PyMatcha/__init__.py

Lines changed: 1 addition & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
from PyMatcha.utils.tables import create_tables
3636
from pymysql.cursors import DictCursor
3737
from redis import StrictRedis
38-
from sentry_sdk import configure_scope
3938
from sentry_sdk.integrations.flask import FlaskIntegration
4039

4140

@@ -127,24 +126,6 @@
127126

128127
jwt = JWTManager(application)
129128

130-
logging.debug("Configuring JWT expired token handler callback")
131-
132-
133-
@jwt.expired_token_loader
134-
def expired_token_callback(expired_token):
135-
logging.debug("Token {} expired".format(expired_token))
136-
resp = {
137-
"code": 401,
138-
"error": {
139-
"message": f"The {expired_token['type']} token has expired",
140-
"name": "Unauthorized Error",
141-
"solution": "Try again when you have renewed your token",
142-
"type": "UnauthorizedError",
143-
},
144-
"success": False,
145-
}
146-
return jsonify(resp), 401
147-
148129

149130
logging.debug("Configuring CORS")
150131
CORS(application, expose_headers="Authorization", supports_credentials=True)
@@ -181,41 +162,6 @@ def expired_token_callback(expired_token):
181162

182163
redis = StrictRedis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True, db=2)
183164

184-
redis.flushdb()
185-
186-
from PyMatcha.models.user import get_user
187-
188-
189-
logging.debug("Configuring JWT user callback loader")
190-
191-
192-
from PyMatcha.utils.errors import NotFoundError
193-
194-
195-
@jwt.user_loader_callback_loader
196-
def jwt_user_callback(identity):
197-
try:
198-
user = get_user(identity["id"])
199-
except NotFoundError:
200-
# The user who the server issues the token for was deleted in the db.
201-
return None
202-
203-
with configure_scope() as scope:
204-
scope.user = {"email": user.email, "id": user.id, "username": user.username}
205-
user.is_online = True
206-
user.date_lastseen = datetime.datetime.utcnow()
207-
user.save()
208-
return user
209-
210-
211-
@jwt.token_in_blacklist_loader
212-
def check_if_token_is_revoked(decrypted_token):
213-
jti = decrypted_token["jti"]
214-
entry = redis.get("is_revoked_jti:" + jti)
215-
if entry is None:
216-
return True
217-
return entry == "true"
218-
219165

220166
from PyMatcha.routes.api.user import user_bp
221167
from PyMatcha.routes.api.auth.email import auth_email_bp
@@ -257,28 +203,10 @@ def check_if_token_is_revoked(decrypted_token):
257203
application.register_blueprint(debug_bp)
258204

259205

260-
@jwt.unauthorized_loader
261-
def no_jwt_callback(error_message):
262-
return (
263-
jsonify(
264-
{
265-
"code": 401,
266-
"error": {
267-
"message": error_message,
268-
"name": "Unauthorized Error",
269-
"solution": "Try again",
270-
"type": "UnauthorizedError",
271-
},
272-
"success": False,
273-
}
274-
),
275-
401,
276-
)
277-
278-
279206
# import tasks here to be registered by celery
280207

281208
import PyMatcha.utils.tasks # noqa
209+
import PyMatcha.utils.jwt_callbacks # noqa
282210

283211

284212
@application.route("/")

backend/PyMatcha/routes/api/auth/login.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525
from flask_jwt_extended import create_refresh_token
2626
from flask_jwt_extended import get_jti
2727
from flask_jwt_extended import get_jwt_identity
28-
from flask_jwt_extended import get_raw_jwt
2928
from flask_jwt_extended import jwt_refresh_token_required
30-
from flask_jwt_extended import jwt_required
3129
from PyMatcha import ACCESS_TOKEN_EXPIRES
3230
from PyMatcha import redis
3331
from PyMatcha import REFRESH_TOKEN_EXPIRES
@@ -89,17 +87,14 @@ def refresh():
8987
return SuccessOutput("access_token", access_token)
9088

9189

92-
@auth_login_bp.route("/auth/access_revoke", methods=["DELETE"])
93-
@jwt_required
90+
@auth_login_bp.route("/auth/logout", methods=["POST"])
91+
@validate_params({"access_token": str, "refresh_token": str})
9492
def logout():
95-
jti = get_raw_jwt()["jti"]
96-
redis.set("is_revoked_jti:" + jti, "true", ACCESS_TOKEN_EXPIRES * 1.2)
97-
return Success("Access token revoked")
98-
99-
100-
@auth_login_bp.route("/auth/refresh_revoke", methods=["DELETE"])
101-
@jwt_refresh_token_required
102-
def logout2():
103-
jti = get_raw_jwt()["jti"]
104-
redis.set("is_revoked_jti:" + jti, "true", REFRESH_TOKEN_EXPIRES * 1.2)
105-
return Success("Refresh token revoked")
93+
data = request.get_json()
94+
access_token = data["access_token"]
95+
refresh_token = data["refresh_token"]
96+
access_jti = get_jti(access_token)
97+
refresh_jti = get_jti(refresh_token)
98+
redis.set("is_revoked_jti:" + access_jti, "true", ACCESS_TOKEN_EXPIRES * 1.2)
99+
redis.set("is_revoked_jti:" + refresh_jti, "true", REFRESH_TOKEN_EXPIRES * 1.2)
100+
return Success("Logout successful.")
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import datetime
2+
import logging
3+
4+
from flask import jsonify
5+
from PyMatcha import jwt
6+
from PyMatcha import redis
7+
from PyMatcha.models.user import get_user
8+
from PyMatcha.utils.errors import NotFoundError
9+
from sentry_sdk import configure_scope
10+
11+
logging.debug("Configuring JWT callbacks")
12+
13+
14+
@jwt.expired_token_loader
15+
def expired_token_callback(expired_token):
16+
resp = {
17+
"code": 401,
18+
"error": {
19+
"message": f"The {expired_token['type']} token has expired",
20+
"name": "Unauthorized Error",
21+
"solution": "Try again when you have renewed your token",
22+
"type": "UnauthorizedError",
23+
},
24+
"success": False,
25+
}
26+
return jsonify(resp), 401
27+
28+
29+
@jwt.user_loader_callback_loader
30+
def jwt_user_callback(identity):
31+
try:
32+
user = get_user(identity["id"])
33+
except NotFoundError:
34+
# The user who the server issues the token for was deleted in the db.
35+
return None
36+
37+
with configure_scope() as scope:
38+
scope.user = {"email": user.email, "id": user.id, "username": user.username}
39+
user.is_online = True
40+
user.date_lastseen = datetime.datetime.utcnow()
41+
user.save()
42+
return user
43+
44+
45+
@jwt.token_in_blacklist_loader
46+
def check_if_token_is_revoked(decrypted_token):
47+
jti = decrypted_token["jti"]
48+
entry = redis.get("is_revoked_jti:" + jti)
49+
if entry is None:
50+
return True
51+
return entry == "true"
52+
53+
54+
@jwt.revoked_token_loader
55+
def jwt_revoked_token_callback():
56+
return (
57+
jsonify(
58+
{
59+
"code": 401,
60+
"error": {
61+
"message": "Token has been revoked.",
62+
"name": "Unauthorized Error",
63+
"solution": "Please login again",
64+
"type": "UnauthorizedError",
65+
},
66+
"success": False,
67+
}
68+
),
69+
401,
70+
)
71+
72+
73+
@jwt.unauthorized_loader
74+
def no_jwt_callback(error_message):
75+
return (
76+
jsonify(
77+
{
78+
"code": 401,
79+
"error": {
80+
"message": error_message,
81+
"name": "Unauthorized Error",
82+
"solution": "Try again",
83+
"type": "UnauthorizedError",
84+
},
85+
"success": False,
86+
}
87+
),
88+
401,
89+
)
90+
91+
92+
@jwt.invalid_token_loader
93+
def jwt_invalid_token_callback(error_message):
94+
return (
95+
jsonify(
96+
{
97+
"code": 400,
98+
"error": {
99+
"message": error_message,
100+
"name": "Bad Request Error",
101+
"solution": "Try again (The token is invalid)",
102+
"type": "BadRequestError",
103+
},
104+
"success": False,
105+
}
106+
),
107+
400,
108+
)

backend/schemas/swagger.yaml

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -160,52 +160,13 @@ paths:
160160
type: string
161161
description: The new and fresh access token
162162
example: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MDAzMjYzMjksIm5iZiI6MTYwMDMyNjMyOSwianRpIjoiZTA0ZWQ3NmMtMTcxMC00ZjQ4LWFkNzMtZDBmOTMxZTkxNzM0IiwiZXhwIjoxNjAwMzI3MjI5LCJpZGVudGl0eSI6eyJpZCI6MSwiZW1haWwiOiJmb29AZXhhbXBsZS5vcmciLCJ1c2VybmFtZSI6ImZvbyIsImlzX29ubGluZSI6MCwiZGF0ZV9sYXN0c2VlbiI6IldlZCwgMTYgU2VwIDIwMjAgMTU6Mzk6MDQgR01UIn0sImZyZXNoIjpmYWxzZSwidHlwZSI6ImFjY2VzcyJ9.dFJMy-04thKA9Q268bgQhiqLDrkqnCOCaiwHJ1XXook
163-
/auth/access_revoke:
163+
/auth/logout:
164164
post:
165165
tags:
166166
- Authentication
167-
summary: Revokes the access token
167+
summary: Logout user
168168
description: ""
169-
operationId: revokeAccessToken
170-
security:
171-
- accessToken: [ ]
172-
responses:
173-
"409":
174-
description: Unauthorized error, token already revoked.
175-
content:
176-
application/json:
177-
schema:
178-
type: object
179-
properties:
180-
msg:
181-
type: string
182-
example: Token has been revoked
183-
default: Token has been revoked
184-
"200":
185-
description: Token successfully revoked
186-
content:
187-
application/json:
188-
schema:
189-
type: object
190-
properties:
191-
success:
192-
type: boolean
193-
description: If the request is a success
194-
example: true
195-
code:
196-
type: integer
197-
description: The status code
198-
example: 200
199-
message:
200-
type: string
201-
example: Access token revoked
202-
/auth/refresh_revoke:
203-
post:
204-
tags:
205-
- Authentication
206-
summary: Revokes the refresh token
207-
description: ""
208-
operationId: revokeRefreshToken
169+
operationId: logoutUser
209170
security:
210171
- refreshToken: [ ]
211172
responses:
@@ -221,7 +182,7 @@ paths:
221182
example: Token has been revoked
222183
default: Token has been revoked
223184
"200":
224-
description: Token successfully revoked
185+
description: Logout successful.
225186
content:
226187
application/json:
227188
schema:
@@ -237,7 +198,7 @@ paths:
237198
example: 200
238199
message:
239200
type: string
240-
example: Refresh token revoked
201+
example: Logout successful.
241202
/auth/confirm/{token}:
242203
post:
243204
summary: Confirm a user with a token

0 commit comments

Comments
 (0)