mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
support jwt on headers
This commit is contained in:
@@ -7,4 +7,8 @@
|
|||||||
- The context menu now doesn't take forever to open up
|
- The context menu now doesn't take forever to open up
|
||||||
- Merged "Save as Playlist" with "Add to Playlist" > "New Playlist"
|
- Merged "Save as Playlist" with "Add to Playlist" > "New Playlist"
|
||||||
|
|
||||||
|
## Bug fixes
|
||||||
|
- Add to queue adding to last index -1
|
||||||
|
-
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
2. Favorites
|
2. Favorites
|
||||||
- Package jsoni and publish on PyPi
|
- Package jsoni and publish on PyPi
|
||||||
- Rewrite stores to use dictionaries instead of list pools
|
- Rewrite stores to use dictionaries instead of list pools
|
||||||
|
- last updated date on tracks added via watchdog is broken
|
||||||
|
- hide "remove from playlist" option on system playlists
|
||||||
|
- Support auth headers
|
||||||
|
|
||||||
# DONE
|
# DONE
|
||||||
- Add recently played playlist
|
- Add recently played playlist
|
||||||
|
|||||||
+1
-1
@@ -65,7 +65,7 @@ def create_api():
|
|||||||
app = OpenAPI(__name__, info=api_info, doc_prefix="/docs")
|
app = OpenAPI(__name__, info=api_info, doc_prefix="/docs")
|
||||||
# JWT CONFIGS
|
# JWT CONFIGS
|
||||||
app.config["JWT_SECRET_KEY"] = UserConfig().userId
|
app.config["JWT_SECRET_KEY"] = UserConfig().userId
|
||||||
app.config["JWT_TOKEN_LOCATION"] = ["cookies"]
|
app.config["JWT_TOKEN_LOCATION"] = ["cookies", "headers"]
|
||||||
app.config["JWT_COOKIE_CSRF_PROTECT"] = False
|
app.config["JWT_COOKIE_CSRF_PROTECT"] = False
|
||||||
app.config["JWT_SESSION_COOKIE"] = False
|
app.config["JWT_SESSION_COOKIE"] = False
|
||||||
|
|
||||||
|
|||||||
+42
-6
@@ -4,7 +4,9 @@ import sqlite3
|
|||||||
from flask import current_app, jsonify
|
from flask import current_app, jsonify
|
||||||
from flask_jwt_extended import (
|
from flask_jwt_extended import (
|
||||||
create_access_token,
|
create_access_token,
|
||||||
|
create_refresh_token,
|
||||||
current_user,
|
current_user,
|
||||||
|
get_jwt_identity,
|
||||||
jwt_required,
|
jwt_required,
|
||||||
set_access_cookies,
|
set_access_cookies,
|
||||||
)
|
)
|
||||||
@@ -37,6 +39,21 @@ def admin_required():
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_token(user: dict):
|
||||||
|
"""
|
||||||
|
Create a new token response
|
||||||
|
"""
|
||||||
|
access_token = create_access_token(identity=user)
|
||||||
|
max_age: int = current_app.config.get("JWT_ACCESS_TOKEN_EXPIRES")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"msg": f"Logged in as {user['username']}",
|
||||||
|
"acccesstoken": access_token,
|
||||||
|
"refreshtoken": create_refresh_token(identity=user),
|
||||||
|
"maxage": max_age,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class LoginBody(BaseModel):
|
class LoginBody(BaseModel):
|
||||||
username: str = Field(description="The username", example="user0")
|
username: str = Field(description="The username", example="user0")
|
||||||
password: str = Field(description="The password", example="password0")
|
password: str = Field(description="The password", example="password0")
|
||||||
@@ -47,7 +64,6 @@ def login(body: LoginBody):
|
|||||||
"""
|
"""
|
||||||
Authenticate using username and password
|
Authenticate using username and password
|
||||||
"""
|
"""
|
||||||
res = jsonify({"msg": f"Logged in as {body.username}"})
|
|
||||||
|
|
||||||
user = authdb.get_user_by_username(body.username)
|
user = authdb.get_user_by_username(body.username)
|
||||||
|
|
||||||
@@ -59,14 +75,28 @@ def login(body: LoginBody):
|
|||||||
if not password_ok:
|
if not password_ok:
|
||||||
return {"msg": "Hehe! invalid password"}, 401
|
return {"msg": "Hehe! invalid password"}, 401
|
||||||
|
|
||||||
access_token = create_access_token(identity=user.todict())
|
res = create_new_token(user.todict())
|
||||||
|
token = res["acccesstoken"]
|
||||||
max_age: int = current_app.config.get("JWT_ACCESS_TOKEN_EXPIRES")
|
age = res["maxage"]
|
||||||
set_access_cookies(res, access_token, max_age=max_age)
|
res = jsonify(res)
|
||||||
|
set_access_cookies(res, token, max_age=age)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@api.post("/refresh")
|
||||||
|
@jwt_required(refresh=True)
|
||||||
|
def refresh():
|
||||||
|
"""
|
||||||
|
Refresh an access token by sending a refresh token in the Authorization header
|
||||||
|
|
||||||
|
>>> Headers:
|
||||||
|
>>> Authorization: Bearer <refresh_token>
|
||||||
|
"""
|
||||||
|
user = get_jwt_identity()
|
||||||
|
return create_new_token(user)
|
||||||
|
|
||||||
|
|
||||||
class UpdateProfileBody(BaseModel):
|
class UpdateProfileBody(BaseModel):
|
||||||
id: int = Field(0, description="The user id")
|
id: int = Field(0, description="The user id")
|
||||||
email: str = Field("", description="The email")
|
email: str = Field("", description="The email")
|
||||||
@@ -77,6 +107,9 @@ class UpdateProfileBody(BaseModel):
|
|||||||
|
|
||||||
@api.put("/profile/update")
|
@api.put("/profile/update")
|
||||||
def update_profile(body: UpdateProfileBody):
|
def update_profile(body: UpdateProfileBody):
|
||||||
|
"""
|
||||||
|
Update user profile
|
||||||
|
"""
|
||||||
user = {
|
user = {
|
||||||
"id": body.id,
|
"id": body.id,
|
||||||
"email": body.email,
|
"email": body.email,
|
||||||
@@ -129,6 +162,9 @@ def update_profile(body: UpdateProfileBody):
|
|||||||
@api.post("/profile/create")
|
@api.post("/profile/create")
|
||||||
@admin_required()
|
@admin_required()
|
||||||
def create_user(body: UpdateProfileBody):
|
def create_user(body: UpdateProfileBody):
|
||||||
|
"""
|
||||||
|
Create a new user
|
||||||
|
"""
|
||||||
if not body.username or not body.password:
|
if not body.username or not body.password:
|
||||||
return {"msg": "Username and password are required"}, 400
|
return {"msg": "Username and password are required"}, 400
|
||||||
|
|
||||||
@@ -199,7 +235,7 @@ def delete_user(body: DeleteUseBody):
|
|||||||
@api.get("/logout")
|
@api.get("/logout")
|
||||||
def logout():
|
def logout():
|
||||||
"""
|
"""
|
||||||
Log out
|
Log out and clear the access token cookie
|
||||||
"""
|
"""
|
||||||
res = jsonify({"msg": "Logged out"})
|
res = jsonify({"msg": "Logged out"})
|
||||||
res.delete_cookie("access_token_cookie")
|
res.delete_cookie("access_token_cookie")
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ class Populate:
|
|||||||
tagged_count = 0
|
tagged_count = 0
|
||||||
|
|
||||||
favs = favdb.get_fav_tracks()
|
favs = favdb.get_fav_tracks()
|
||||||
|
|
||||||
records = dict()
|
records = dict()
|
||||||
|
|
||||||
for fav in favs:
|
for fav in favs:
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ mimetypes.add_type("application/manifest+json", ".webmanifest")
|
|||||||
werkzeug = logging.getLogger("werkzeug")
|
werkzeug = logging.getLogger("werkzeug")
|
||||||
werkzeug.setLevel(logging.ERROR)
|
werkzeug.setLevel(logging.ERROR)
|
||||||
|
|
||||||
|
|
||||||
# Background tasks
|
# Background tasks
|
||||||
@background
|
@background
|
||||||
def bg_run_setup():
|
def bg_run_setup():
|
||||||
@@ -83,25 +84,32 @@ app = create_api()
|
|||||||
app.static_folder = get_home_res_path("client")
|
app.static_folder = get_home_res_path("client")
|
||||||
|
|
||||||
# INFO: Routes that don't need authentication
|
# INFO: Routes that don't need authentication
|
||||||
whitelisted_routes = {"/auth/login", "/auth/users", "/auth/logout", "/docs"}
|
whitelisted_routes = {"/auth/login", "/auth/users", "/auth/logout", "/auth/refresh", "/docs"}
|
||||||
blacklist_extensions = {".webp"}.union(getClientFilesExtensions())
|
blacklist_extensions = {".webp"}.union(getClientFilesExtensions())
|
||||||
|
|
||||||
|
|
||||||
|
def skipAuthAction():
|
||||||
|
"""
|
||||||
|
Skips the JWT verification for the current request.
|
||||||
|
"""
|
||||||
|
if request.path == "/" or any(
|
||||||
|
request.path.endswith(ext) for ext in blacklist_extensions
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# if request path starts with any of the blacklisted routes, don't verify jwt
|
||||||
|
if any(request.path.startswith(route) for route in whitelisted_routes):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def verify_auth():
|
def verify_auth():
|
||||||
"""
|
"""
|
||||||
Verifies the JWT token before each request.
|
Verifies the JWT token before each request.
|
||||||
"""
|
"""
|
||||||
if request.path == "/" or any(
|
if skipAuthAction():
|
||||||
request.path.endswith(ext) for ext in blacklist_extensions
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
# if request path starts with any of the blacklisted routes, don't verify jwt
|
|
||||||
if any(request.path.startswith(route) for route in whitelisted_routes):
|
|
||||||
# print(
|
|
||||||
# "Found whitelisted route: ", request.path, "... Skipping jwt verification"
|
|
||||||
# )
|
|
||||||
return
|
return
|
||||||
|
|
||||||
verify_jwt_in_request()
|
verify_jwt_in_request()
|
||||||
@@ -110,8 +118,14 @@ def verify_auth():
|
|||||||
@app.after_request
|
@app.after_request
|
||||||
def refresh_expiring_jwt(response: Response):
|
def refresh_expiring_jwt(response: Response):
|
||||||
"""
|
"""
|
||||||
Refreshes the JWT token after each request.
|
Refreshes the cookies JWT token after each request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# INFO: If the request has an Authorization header, don't refresh the jwt
|
||||||
|
# Request is probably from the mobile client or a third party
|
||||||
|
if skipAuthAction() or request.headers.get("Authorization"):
|
||||||
|
return response
|
||||||
|
|
||||||
try:
|
try:
|
||||||
exp_timestamp = get_jwt()["exp"]
|
exp_timestamp = get_jwt()["exp"]
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
|
|||||||
Reference in New Issue
Block a user