add routes to create user

+ route to delete user
+ add admin_required decorator
This commit is contained in:
mungai-njoroge
2024-04-27 10:05:15 +03:00
parent 1eeab2d49e
commit 0ff5661765
4 changed files with 138 additions and 19 deletions
+3 -4
View File
@@ -1,7 +1,6 @@
# What's New?
- Hovering on recent favorite item will show how long ago it was ♥ed
- Recently added playlist returns a max of 100 tracks, but without a cutoff period
<!-- TODO: ELABORATE -->
- Auth
# Development
- API documentation on /openapi
## Development
+115 -7
View File
@@ -1,5 +1,6 @@
from dataclasses import asdict
import json
from dataclasses import asdict
from functools import wraps
from flask import jsonify
from flask_jwt_extended import create_access_token, current_user, set_access_cookies
from pydantic import BaseModel, Field
@@ -7,12 +8,29 @@ from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint
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])
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):
username: str = Field(description="The username", example="user0")
password: str = Field(description="The password", example="password0")
@@ -49,7 +67,6 @@ class UpdateProfileBody(BaseModel):
@api.put("/profile/update")
def update_profile(body: UpdateProfileBody):
user = {
"id": current_user["id"],
"email": body.email,
@@ -68,11 +85,84 @@ def update_profile(body: UpdateProfileBody):
else:
user.pop("roles")
if user["password"]:
user["password"] = encode_password(user["password"])
# remove empty values
clean_user = {k: v for k, v in user.items() if v}
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")
def logout():
"""
@@ -83,13 +173,32 @@ def logout():
return res
class GetAllUsersQuery(BaseModel):
simplified: bool = Field(
False, description="Whether to return simplified user data"
)
@api.get("/users")
def get_all_users():
def get_all_users(query: GetAllUsersQuery):
"""
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")
@@ -97,5 +206,4 @@ def get_logged_in_user():
"""
Get logged in user
"""
print("current_user", current_user)
return dict(current_user)
+17 -5
View File
@@ -28,11 +28,12 @@ class SQLiteAuthMethods:
with SQLiteManager(userdata_db=True) as cur:
cur = cur.execute(sql, user_tuple)
userid = cur.lastrowid
if userid:
user = SQLiteAuthMethods.get_user_by_id(userid).todict_simplified()
cur.close()
return user
return userid
# if userid:
# # sleep
# user = SQLiteAuthMethods.get_user_by_id(userid).todict_simplified()
# cur.close()
# return user
raise Exception(f"Failed to insert user: {user}")
@@ -131,3 +132,14 @@ class SQLiteAuthMethods:
return User(*data)
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()
+3 -3
View File
@@ -60,9 +60,9 @@ def verify_auth():
# 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):
print(
"Found blacklisted route: ", request.path, "... Skipping jwt verification"
)
# print(
# "Found blacklisted route: ", request.path, "... Skipping jwt verification"
# )
return
verify_jwt_in_request()