mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
add routes to create user
+ route to delete user + add admin_required decorator
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
# What's New?
|
# What's New?
|
||||||
|
|
||||||
- Hovering on recent favorite item will show how long ago it was ♥ed
|
<!-- TODO: ELABORATE -->
|
||||||
- Recently added playlist returns a max of 100 tracks, but without a cutoff period
|
- Auth
|
||||||
|
|
||||||
# Development
|
## Development
|
||||||
- API documentation on /openapi
|
|
||||||
|
|||||||
+115
-7
@@ -1,5 +1,6 @@
|
|||||||
from dataclasses import asdict
|
|
||||||
import json
|
import json
|
||||||
|
from dataclasses import asdict
|
||||||
|
from functools import wraps
|
||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
from flask_jwt_extended import create_access_token, current_user, set_access_cookies
|
from flask_jwt_extended import create_access_token, current_user, set_access_cookies
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -7,12 +8,29 @@ from flask_openapi3 import Tag
|
|||||||
from flask_openapi3 import APIBlueprint
|
from flask_openapi3 import APIBlueprint
|
||||||
|
|
||||||
from app.db.sqlite.auth import SQLiteAuthMethods as authdb
|
from app.db.sqlite.auth import SQLiteAuthMethods as authdb
|
||||||
from app.utils.auth import check_password
|
from app.utils.auth import check_password, encode_password
|
||||||
|
|
||||||
bp_tag = Tag(name="Auth", description="Authentication")
|
bp_tag = Tag(name="Auth", description="Authentication stuff")
|
||||||
api = APIBlueprint("auth", __name__, url_prefix="/auth", abp_tags=[bp_tag])
|
api = APIBlueprint("auth", __name__, url_prefix="/auth", abp_tags=[bp_tag])
|
||||||
|
|
||||||
|
|
||||||
|
def admin_required():
|
||||||
|
"""
|
||||||
|
Decorator to require admin role
|
||||||
|
"""
|
||||||
|
|
||||||
|
def wrapper(fn):
|
||||||
|
@wraps(fn)
|
||||||
|
def decorator(*args, **kwargs):
|
||||||
|
if "admin" not in current_user["roles"]:
|
||||||
|
return {"msg": "Only admins can do that!"}, 403
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
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")
|
||||||
@@ -49,7 +67,6 @@ class UpdateProfileBody(BaseModel):
|
|||||||
|
|
||||||
@api.put("/profile/update")
|
@api.put("/profile/update")
|
||||||
def update_profile(body: UpdateProfileBody):
|
def update_profile(body: UpdateProfileBody):
|
||||||
|
|
||||||
user = {
|
user = {
|
||||||
"id": current_user["id"],
|
"id": current_user["id"],
|
||||||
"email": body.email,
|
"email": body.email,
|
||||||
@@ -68,11 +85,84 @@ def update_profile(body: UpdateProfileBody):
|
|||||||
else:
|
else:
|
||||||
user.pop("roles")
|
user.pop("roles")
|
||||||
|
|
||||||
|
if user["password"]:
|
||||||
|
user["password"] = encode_password(user["password"])
|
||||||
|
|
||||||
# remove empty values
|
# remove empty values
|
||||||
clean_user = {k: v for k, v in user.items() if v}
|
clean_user = {k: v for k, v in user.items() if v}
|
||||||
return authdb.update_user(clean_user)
|
return authdb.update_user(clean_user)
|
||||||
|
|
||||||
|
|
||||||
|
@api.post("/profile/create")
|
||||||
|
@admin_required()
|
||||||
|
def create_user(body: UpdateProfileBody):
|
||||||
|
if not body.username or not body.password:
|
||||||
|
return {"msg": "Username and password are required"}, 400
|
||||||
|
|
||||||
|
user = {
|
||||||
|
"username": body.username,
|
||||||
|
"password": encode_password(body.password),
|
||||||
|
"roles": json.dumps([]),
|
||||||
|
}
|
||||||
|
|
||||||
|
# check if user already exists
|
||||||
|
if authdb.get_user_by_username(user["username"]):
|
||||||
|
return {"msg": "Username already exists"}, 400
|
||||||
|
|
||||||
|
userid = authdb.insert_user(user)
|
||||||
|
return authdb.get_user_by_id(userid).todict()
|
||||||
|
|
||||||
|
|
||||||
|
@api.post("/profile/guest/create")
|
||||||
|
@admin_required()
|
||||||
|
def create_guest_user():
|
||||||
|
"""
|
||||||
|
Create a guest user
|
||||||
|
"""
|
||||||
|
# check if guest user already exists
|
||||||
|
guest_user = authdb.get_user_by_username("guest")
|
||||||
|
|
||||||
|
if guest_user:
|
||||||
|
return {
|
||||||
|
"msg": "Guest user already exists",
|
||||||
|
}, 400
|
||||||
|
|
||||||
|
userid = authdb.insert_guest_user()
|
||||||
|
|
||||||
|
if userid:
|
||||||
|
return {
|
||||||
|
"msg": "Guest user created",
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"msg": "Failed to create guest user",
|
||||||
|
}, 500
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteUseBody(BaseModel):
|
||||||
|
username: str = Field("", description="The username")
|
||||||
|
|
||||||
|
|
||||||
|
@api.delete("/profile/delete")
|
||||||
|
@admin_required()
|
||||||
|
def delete_user(body: DeleteUseBody):
|
||||||
|
"""
|
||||||
|
Delete a user by username
|
||||||
|
"""
|
||||||
|
# prevent admin from deleting themselves
|
||||||
|
if body.username == current_user["usrname"]:
|
||||||
|
return {"msg": "Sorry! you cannot delete yourselfu"}, 400
|
||||||
|
|
||||||
|
# prevent deleting the only admin
|
||||||
|
users = authdb.get_all_users()
|
||||||
|
admins = [user for user in users if "admin" in user.roles]
|
||||||
|
if len(admins) == 1 and admins[0].username == body.username:
|
||||||
|
return {"msg": "Cannot delete the only admin"}, 400
|
||||||
|
|
||||||
|
authdb.delete_user_by_username(body.username)
|
||||||
|
return {"msg": f"User {body.username} deleted"}
|
||||||
|
|
||||||
|
|
||||||
@api.get("/logout")
|
@api.get("/logout")
|
||||||
def logout():
|
def logout():
|
||||||
"""
|
"""
|
||||||
@@ -83,13 +173,32 @@ def logout():
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class GetAllUsersQuery(BaseModel):
|
||||||
|
simplified: bool = Field(
|
||||||
|
False, description="Whether to return simplified user data"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@api.get("/users")
|
@api.get("/users")
|
||||||
def get_all_users():
|
def get_all_users(query: GetAllUsersQuery):
|
||||||
"""
|
"""
|
||||||
Get all users
|
Get all users
|
||||||
"""
|
"""
|
||||||
users = authdb.get_all_users()
|
users = authdb.get_all_users()
|
||||||
return [user.todict_simplified() for user in users]
|
|
||||||
|
# remove guest user
|
||||||
|
users = [user for user in users if user.username != "guest"]
|
||||||
|
|
||||||
|
# reverse list to show latest users first
|
||||||
|
users = list(reversed(users))
|
||||||
|
|
||||||
|
# bring admins to the front
|
||||||
|
users = sorted(users, key=lambda x: "admin" in x.roles, reverse=True)
|
||||||
|
|
||||||
|
if query.simplified:
|
||||||
|
return [user.todict_simplified() for user in users]
|
||||||
|
|
||||||
|
return [user.todict() for user in users]
|
||||||
|
|
||||||
|
|
||||||
@api.route("/user")
|
@api.route("/user")
|
||||||
@@ -97,5 +206,4 @@ def get_logged_in_user():
|
|||||||
"""
|
"""
|
||||||
Get logged in user
|
Get logged in user
|
||||||
"""
|
"""
|
||||||
print("current_user", current_user)
|
|
||||||
return dict(current_user)
|
return dict(current_user)
|
||||||
|
|||||||
+17
-5
@@ -28,11 +28,12 @@ class SQLiteAuthMethods:
|
|||||||
with SQLiteManager(userdata_db=True) as cur:
|
with SQLiteManager(userdata_db=True) as cur:
|
||||||
cur = cur.execute(sql, user_tuple)
|
cur = cur.execute(sql, user_tuple)
|
||||||
userid = cur.lastrowid
|
userid = cur.lastrowid
|
||||||
|
return userid
|
||||||
if userid:
|
# if userid:
|
||||||
user = SQLiteAuthMethods.get_user_by_id(userid).todict_simplified()
|
# # sleep
|
||||||
cur.close()
|
# user = SQLiteAuthMethods.get_user_by_id(userid).todict_simplified()
|
||||||
return user
|
# cur.close()
|
||||||
|
# return user
|
||||||
|
|
||||||
raise Exception(f"Failed to insert user: {user}")
|
raise Exception(f"Failed to insert user: {user}")
|
||||||
|
|
||||||
@@ -131,3 +132,14 @@ class SQLiteAuthMethods:
|
|||||||
return User(*data)
|
return User(*data)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_user_by_username(username: str):
|
||||||
|
"""
|
||||||
|
Delete a user by username.
|
||||||
|
"""
|
||||||
|
sql = "DELETE FROM users WHERE username = ?"
|
||||||
|
|
||||||
|
with SQLiteManager(userdata_db=True) as cur:
|
||||||
|
cur.execute(sql, (username,))
|
||||||
|
cur.close()
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ def verify_auth():
|
|||||||
|
|
||||||
# if request path starts with any of the blacklisted routes, don't verify jwt
|
# if request path starts with any of the blacklisted routes, don't verify jwt
|
||||||
if any(request.path.startswith(route) for route in blacklist_routes):
|
if any(request.path.startswith(route) for route in blacklist_routes):
|
||||||
print(
|
# print(
|
||||||
"Found blacklisted route: ", request.path, "... Skipping jwt verification"
|
# "Found blacklisted route: ", request.path, "... Skipping jwt verification"
|
||||||
)
|
# )
|
||||||
return
|
return
|
||||||
|
|
||||||
verify_jwt_in_request()
|
verify_jwt_in_request()
|
||||||
|
|||||||
Reference in New Issue
Block a user