|
| 1 | +from flask import Flask, request, jsonify |
| 2 | +from werkzeug.security import generate_password_hash, check_password_hash |
| 3 | +import jwt |
| 4 | +import datetime |
| 5 | +from functools import wraps |
| 6 | + |
| 7 | +app = Flask(__name__) |
| 8 | +# It's good practice to load this from environment variables in a real application |
| 9 | +app.config["SECRET_KEY"] = "unsafe_secret" |
| 10 | + |
| 11 | +# In-memory storage to remove the database dependency for simplicity |
| 12 | +users = {} |
| 13 | +items = {} |
| 14 | +next_item_id = 1 |
| 15 | + |
| 16 | +# JWT token decorator to protect routes |
| 17 | +def token_required(f): |
| 18 | + @wraps(f) |
| 19 | + def decorated(*args, **kwargs): |
| 20 | + token = None |
| 21 | + if 'Authorization' in request.headers: |
| 22 | + # Expecting token format: "Bearer <token>" |
| 23 | + try: |
| 24 | + token = request.headers['Authorization'].split(" ")[1] |
| 25 | + except IndexError: |
| 26 | + return jsonify({'message': 'Malformed token header!'}), 400 |
| 27 | + |
| 28 | + if not token: |
| 29 | + return jsonify({'message': 'Token is missing!'}), 401 |
| 30 | + |
| 31 | + try: |
| 32 | + # Decode the token to validate it and get the user's identity |
| 33 | + data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) |
| 34 | + # Check if the user from the token exists in our store |
| 35 | + if data['username'] not in users: |
| 36 | + return jsonify({'message': 'User from token not found!'}), 401 |
| 37 | + current_user = data['username'] |
| 38 | + except jwt.ExpiredSignatureError: |
| 39 | + return jsonify({'message': 'Token has expired!'}), 401 |
| 40 | + except Exception as e: |
| 41 | + return jsonify({'message': 'Token is invalid!', 'error': str(e)}), 401 |
| 42 | + |
| 43 | + # Pass the current user's username to the decorated function |
| 44 | + return f(current_user, *args, **kwargs) |
| 45 | + return decorated |
| 46 | + |
| 47 | +@app.route('/login', methods=['POST']) |
| 48 | +def login_user(): |
| 49 | + auth = request.json |
| 50 | + if not auth or not auth.get('username') or not auth.get('password'): |
| 51 | + return jsonify({'message': 'Could not verify, missing username or password'}), 401 |
| 52 | + |
| 53 | + username = auth['username'] |
| 54 | + user_password_hash = users.get(username) |
| 55 | + |
| 56 | + if not user_password_hash: |
| 57 | + return jsonify({'message': 'User not found'}), 401 |
| 58 | + |
| 59 | + if check_password_hash(user_password_hash, auth['password']): |
| 60 | + # Create a token with a 2-minute expiration time |
| 61 | + token = jwt.encode({ |
| 62 | + 'username': username, |
| 63 | + 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=2) |
| 64 | + }, app.config['SECRET_KEY'], algorithm="HS256") |
| 65 | + |
| 66 | + return jsonify({'token': token}) |
| 67 | + |
| 68 | + return jsonify({'message': 'Password is wrong'}), 403 |
| 69 | + |
| 70 | +# --- CRUD Operations for a simple "item" resource --- |
| 71 | + |
| 72 | +@app.route('/item', methods=['POST']) |
| 73 | +@token_required |
| 74 | +def add_item(current_user): |
| 75 | + global next_item_id |
| 76 | + data = request.json |
| 77 | + if not data: |
| 78 | + return jsonify({"message": "No input data provided"}), 400 |
| 79 | + |
| 80 | + item_id_str = str(next_item_id) |
| 81 | + # Store item data associated with the user who created it |
| 82 | + items[item_id_str] = {'data': data, 'owner': current_user} |
| 83 | + next_item_id += 1 |
| 84 | + |
| 85 | + # Return the ID of the newly created item |
| 86 | + return jsonify({'message': 'Item added', 'id': item_id_str}), 201 |
| 87 | + |
| 88 | +@app.route('/item/<id>', methods=['GET']) |
| 89 | +@token_required |
| 90 | +def get_item(current_user, id): |
| 91 | + item = items.get(id) |
| 92 | + if item: |
| 93 | + # For simplicity, any authenticated user can view any item. |
| 94 | + return jsonify(item['data']), 200 |
| 95 | + else: |
| 96 | + return jsonify({'message': 'Item not found'}), 404 |
| 97 | + |
| 98 | +@app.route('/item/<id>', methods=['PUT']) |
| 99 | +@token_required |
| 100 | +def update_item(current_user, id): |
| 101 | + data = request.json |
| 102 | + if not data: |
| 103 | + return jsonify({"message": "No input data provided"}), 400 |
| 104 | + |
| 105 | + if id in items: |
| 106 | + # Simple authorization: only the owner can update their own item |
| 107 | + if items[id]['owner'] != current_user: |
| 108 | + return jsonify({'message': 'Permission denied: you are not the owner'}), 403 |
| 109 | + items[id]['data'].update(data) |
| 110 | + return jsonify({'message': 'Item updated'}), 200 |
| 111 | + else: |
| 112 | + return jsonify({'message': 'Item not found'}), 404 |
| 113 | + |
| 114 | +@app.route('/item/<id>', methods=['DELETE']) |
| 115 | +@token_required |
| 116 | +def delete_item(current_user, id): |
| 117 | + if id in items: |
| 118 | + # Simple authorization: only the owner can delete their own item |
| 119 | + if items[id]['owner'] != current_user: |
| 120 | + return jsonify({'message': 'Permission denied: you are not the owner'}), 403 |
| 121 | + del items[id] |
| 122 | + return jsonify({'message': 'Item deleted'}), 200 |
| 123 | + else: |
| 124 | + return jsonify({'message': 'Item not found'}), 404 |
| 125 | + |
| 126 | +# --- User Management --- |
| 127 | + |
| 128 | +@app.route('/register', methods=['POST']) |
| 129 | +def register_user(): |
| 130 | + data = request.get_json() |
| 131 | + username = data.get('username') |
| 132 | + password = data.get('password') |
| 133 | + |
| 134 | + if not username or not password: |
| 135 | + return jsonify({'message': 'Missing username or password'}), 400 |
| 136 | + |
| 137 | + if username in users: |
| 138 | + return jsonify({'message': 'User already exists'}), 409 |
| 139 | + |
| 140 | + hashed_password = generate_password_hash(password) |
| 141 | + users[username] = hashed_password |
| 142 | + |
| 143 | + return jsonify({'message': 'User registered successfully'}), 201 |
| 144 | + |
| 145 | +@app.route('/user/delete/<username>', methods=['DELETE']) |
| 146 | +@token_required |
| 147 | +def delete_user_by_username(current_user, username): |
| 148 | + # Simple authorization: a user can only delete their own account |
| 149 | + if current_user != username: |
| 150 | + return jsonify({'message': 'Permission denied: you can only delete your own account'}), 403 |
| 151 | + |
| 152 | + if username in users: |
| 153 | + del users[username] |
| 154 | + # Clean up items owned by the deleted user |
| 155 | + items_to_delete = [item_id for item_id, item in items.items() if item['owner'] == username] |
| 156 | + for item_id in items_to_delete: |
| 157 | + del items[item_id] |
| 158 | + return jsonify({'message': 'User and their items deleted successfully'}), 200 |
| 159 | + else: |
| 160 | + return jsonify({'message': 'User not found'}), 404 |
| 161 | + |
| 162 | +if __name__ == '__main__': |
| 163 | + # Binds to all network interfaces, making it accessible for testing |
| 164 | + app.run(host='0.0.0.0', port=5000, debug=False) |
0 commit comments