added docstrings
This commit is contained in:
parent
b44177ce47
commit
e3ff820ec6
@ -15,6 +15,14 @@ app = Bottle()
|
||||
|
||||
|
||||
def username_by_token(request) -> str | None:
|
||||
"""
|
||||
Extract username from JWT token in request cookies.
|
||||
Returns non on jwt.ExpiredSignatureError and jwt.InvalidTokenError
|
||||
|
||||
:param request: Bottle request object
|
||||
|
||||
:return: username or None
|
||||
"""
|
||||
token = request.get_cookie("oauth2")
|
||||
if not token:
|
||||
return None
|
||||
@ -33,6 +41,15 @@ def user_guard(reyection_msg: str = "Requires authentication", allow_anonymous:
|
||||
def user_guard_decorator(fn: callable):
|
||||
@wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
"""
|
||||
Given a bottle request, try authenticating the user by its cookies.
|
||||
If authentication fails, return a 401 with the given reyection_msg.
|
||||
Otherwise, call the decorated function with the user as first argument.
|
||||
|
||||
:param args: arguments to pass to decorated function
|
||||
:param kwargs: keyword arguments to pass to decorated function
|
||||
:return: decorated function result or 401 rejection
|
||||
"""
|
||||
username = username_by_token(request)
|
||||
if not username and not allow_anonymous:
|
||||
response.status = 401
|
||||
@ -48,6 +65,10 @@ def admin_guard(reyection_msg: str = "Requires admin priveledges"):
|
||||
@wraps(fn)
|
||||
@user_guard(reyection_msg)
|
||||
def wrapper(user, *args, **kwargs):
|
||||
"""
|
||||
Check if an authenticated user has admin priveledges.
|
||||
user_guard is used to ensure authentication.
|
||||
"""
|
||||
if user.role != "admin":
|
||||
response.status = 401
|
||||
return dumps({"error": reyection_msg})
|
||||
@ -58,6 +79,13 @@ def admin_guard(reyection_msg: str = "Requires admin priveledges"):
|
||||
|
||||
@app.route("/token", method=["POST"])
|
||||
def token():
|
||||
"""
|
||||
Login endpoint that returns a JWT token on successful authentication.
|
||||
Expects JSON body with "user" and "password" fields.
|
||||
Sets JWT token as cookie "oauth2".
|
||||
|
||||
:return: JSON with JWT token or error message
|
||||
"""
|
||||
body = request.body.read()
|
||||
try:
|
||||
data = loads(body.decode('utf-8'))
|
||||
@ -88,6 +116,11 @@ def token():
|
||||
|
||||
@app.route("/", method=["GET"])
|
||||
def get_user():
|
||||
"""
|
||||
Get the username of the authenticated user from the JWT token.
|
||||
If no valid token is present, returns 401 Unauthorized.
|
||||
This is a helper endpoint with no real usecase.
|
||||
"""
|
||||
username = username_by_token(request)
|
||||
if not username:
|
||||
response.status = 401
|
||||
@ -99,6 +132,15 @@ def get_user():
|
||||
@app.route("/add", method=["POST"])
|
||||
@admin_guard()
|
||||
def add_user(user):
|
||||
"""
|
||||
Add a new user to the system.
|
||||
Expects JSON body with "new_user", "new_password", and optional "new_admin" fields.
|
||||
Rejected by methodes of admin_guard if the requester is not an admin.
|
||||
|
||||
:param user: authenticated admin user object
|
||||
|
||||
:return: JSON message indicating success or error
|
||||
"""
|
||||
data = read_keys_from_request(["new_user", "new_password", "new_admin"])
|
||||
role = "admin" if data.get("new_admin", False) else "user"
|
||||
response.content_type = 'application/json'
|
||||
@ -113,6 +155,16 @@ def add_user(user):
|
||||
@app.route("/changePassword", method=["POST"])
|
||||
@user_guard()
|
||||
def change_password(user):
|
||||
"""
|
||||
Request password change for the authenticated user.
|
||||
Rejected by user_guard if the requester is not authenticated.
|
||||
Rejected whe the old password does not match.
|
||||
Expects JSON body with "old_password" and "new_password" fields.
|
||||
|
||||
:param user: authenticated user object
|
||||
|
||||
:return: JSON message indicating success or error
|
||||
"""
|
||||
data = read_keys_from_request(["new_password", "old_password"])
|
||||
response.content_type = 'application/json'
|
||||
try:
|
||||
@ -129,6 +181,16 @@ def change_password(user):
|
||||
@app.route("/delete/<deletion_target>", method=["POST"])
|
||||
@user_guard()
|
||||
def delete_user(user, deletion_target: str):
|
||||
"""
|
||||
Delete a user from the system.
|
||||
The authenticated user can delete itself or an admin can delete any user.
|
||||
Expects the username of the user to delete as a URL parameter.
|
||||
|
||||
:param user: authenticated user object
|
||||
:param deletion_target: username of the user to delete
|
||||
|
||||
:return: JSON message indicating success or error
|
||||
"""
|
||||
response.content_type = 'application/json'
|
||||
is_admin = admin_guard()(lambda _: True)() == True
|
||||
# Note: we could just use user.role == "admin" but we
|
||||
@ -148,6 +210,12 @@ def delete_user(user, deletion_target: str):
|
||||
@app.route("/getAll", method=["GET"])
|
||||
@admin_guard()
|
||||
def get_all_users(_):
|
||||
"""
|
||||
Get a list of all users in the system.
|
||||
Rejected by admin_guard if the requester is not an admin.
|
||||
|
||||
:return: JSON list of users with their names and roles
|
||||
"""
|
||||
response.content_type = 'application/json'
|
||||
users = request.db_connector.get_user(None)
|
||||
user_list = [{"name": u.name, "role": u.role} for u in users]
|
||||
|
||||
@ -8,6 +8,12 @@ from simple_chat_api.utils import read_keys_from_request
|
||||
app = Bottle()
|
||||
|
||||
def serialize_message(messages: list[Message]) -> str:
|
||||
"""
|
||||
Serialize a list of Message objects into a JSON string.
|
||||
|
||||
:param messages: list of Message objects
|
||||
:return: JSON string representing the messages
|
||||
"""
|
||||
return dumps({getattr(msg, "id"):
|
||||
{key: getattr(msg, key)
|
||||
for key in msg.__dict__.keys() if key[0] != '_' and key != 'id'}
|
||||
@ -16,6 +22,16 @@ def serialize_message(messages: list[Message]) -> str:
|
||||
@app.route('/<room>', method=['POST'])
|
||||
@user_guard()
|
||||
def recive_msg(user: User, room: str):
|
||||
"""
|
||||
Receive a new message for a specific room.
|
||||
Rejected by user_guard if the requester is not authenticated.
|
||||
Expects JSON body with "content" field.
|
||||
|
||||
:param user: authenticated user object
|
||||
:param room: room to add the message to
|
||||
|
||||
:return: JSON string representing the newly added message
|
||||
"""
|
||||
msg = read_keys_from_request(keys=["content"])
|
||||
if not msg:
|
||||
response.status = 400
|
||||
@ -27,6 +43,16 @@ def recive_msg(user: User, room: str):
|
||||
@app.route('/<room>', method=['GET'])
|
||||
@user_guard()
|
||||
def return_msgs(user: User, room: str):
|
||||
"""
|
||||
Get messages from a specific room.
|
||||
Rejected by user_guard if the requester is not authenticated.
|
||||
Accepts optional 'since' query parameter to get messages after a specific timestamp.
|
||||
|
||||
:param user: authenticated user object
|
||||
:param room: room to get messages from
|
||||
|
||||
:return: JSON string representing the messages from the room
|
||||
"""
|
||||
since = request.query.get('since', None)
|
||||
if since:
|
||||
try:
|
||||
|
||||
@ -15,6 +15,11 @@ app = Bottle()
|
||||
|
||||
|
||||
def initialize_app():
|
||||
"""
|
||||
Construct API, add Middleware, etc.
|
||||
|
||||
:return: Bottle app
|
||||
"""
|
||||
db = DbConnector(config.db_url)
|
||||
|
||||
@app.hook('before_request')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user