Adding API Key Authentication to a Flask App (+98, -3)

app.py (+3, -1)

From: /before-custom-decorator/app.py

To: /after-custom-decorator/app.py

            
            index 0ac8801..5c1335f 100644
--- a/tmp/repos/LUS24/example_flask_restful_custom_api_key/before-custom-decorator/app.py
+++ b/tmp/repos/LUS24/example_flask_restful_custom_api_key/after-custom-decorator/app.py
@@ -7,6 +7,7 @@ from security import authenticate, identity
 from resources.user import UserRegister
 from resources.item import Item, ItemList
 from resources.store import Store, StoreList
+from resources.device import AddDevice
 
 app = Flask(__name__)
 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db'
@@ -22,10 +23,11 @@ def create_tables():
     db.create_all()
 
 
-jwt = JWT(app, authenticate, identity)  # /auth
+jwt = JWT(app, authenticate, identity)
 
 api.add_resource(Store, '/store/<string:name>')
 api.add_resource(StoreList, '/stores')
 api.add_resource(Item, '/item/<string:name>')
 api.add_resource(ItemList, '/items')
 api.add_resource(UserRegister, '/register')
+api.add_resource(AddDevice, '/user/add-device')
        

device.py (+39, -0)

From: /after-custom-decorator/models/device.py

To: /after-custom-decorator/models/device.py

            
            new file mode 100644
index 0000000..2770ccf
--- /dev/null
+++ b/tmp/repos/LUS24/example_flask_restful_custom_api_key/after-custom-decorator/models/device.py
@@ -0,0 +1,39 @@
+from db import db
+import uuid
+
+class DeviceModel(db.Model):
+    __tablename__ = 'devices'
+
+    id = db.Column(db.Integer, primary_key = True)
+    device_name = db.Column(db.String(80))
+    device_key = db.Column(db.String(80))
+    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
+    user = db.relationship('UserModel', back_populates="devices")
+
+    def __init__(self, device_name, user_id, device_key=None):
+        self.device_name = device_name
+        self.user_id = user_id
+        self.device_key = device_key or uuid.uuid4().hex
+
+    def json(self):
+        return {
+            'device_name': self.device_name, 
+            'device_key': self.device_key, 
+            'user_id': self.user_id
+        }
+
+    @classmethod
+    def find_by_name(cls, device_name):
+        return cls.query.filter_by(device_name=device_name).first()
+
+    @classmethod
+    def find_by_device_key(cls, device_key):
+        return cls.query.filter_by(device_key=device_key).first()
+
+    def save_to_db(self):
+        db.session.add(self)
+        db.session.commit()
+
+    def delete_from_db(self):
+        db.session.delete(self)
+        db.session.commit()
        

device.py (+28, -0)

From: /after-custom-decorator/resources/device.py

To: /after-custom-decorator/resources/device.py

            
            new file mode 100644
index 0000000..ca80a89
--- /dev/null
+++ b/tmp/repos/LUS24/example_flask_restful_custom_api_key/after-custom-decorator/resources/device.py
@@ -0,0 +1,28 @@
+from flask_restful import Resource, reqparse
+from flask_jwt import jwt_required, current_identity
+from models.device import DeviceModel
+
+
+class AddDevice(Resource):
+    parser = reqparse.RequestParser()
+    parser.add_argument(
+        'device_name',
+        type=str,
+        required=True
+        )
+
+    @jwt_required()
+    def post(self):
+        data = AddDevice.parser.parse_args()
+        name = data["device_name"]
+
+        if DeviceModel.find_by_name(data["device_name"]):
+            return {'message': f"A device with name '{name}' already exists."}, 400
+
+        new_device = DeviceModel(
+            device_name=name,
+            user_id=current_identity.id
+        )
+        new_device.save_to_db()
+
+        return  {"api_key": new_device.device_key}, 201
        

item.py (+0, -1)

From: /before-custom-decorator/resources/item.py

To: /after-custom-decorator/resources/item.py

            
            index c963804..98fad95 100644
--- a/tmp/repos/LUS24/example_flask_restful_custom_api_key/before-custom-decorator/resources/item.py
+++ b/tmp/repos/LUS24/example_flask_restful_custom_api_key/after-custom-decorator/resources/item.py
@@ -63,4 +63,3 @@ class Item(Resource):
 class ItemList(Resource):
     def get(self):
         return {'items': list(map(lambda x: x.json(), ItemModel.query.all()))}
-
        

store.py (+2, -0)

From: /before-custom-decorator/resources/store.py

To: /after-custom-decorator/resources/store.py

            
            index 74cf4cb..28d227d 100644
--- a/tmp/repos/LUS24/example_flask_restful_custom_api_key/before-custom-decorator/resources/store.py
+++ b/tmp/repos/LUS24/example_flask_restful_custom_api_key/after-custom-decorator/resources/store.py
@@ -1,5 +1,6 @@
 from flask_restful import Resource
 from models.store import StoreModel
+from security import api_required
 
 
 class Store(Resource):
@@ -10,6 +11,7 @@ class Store(Resource):
             return store.json()
         return {'message': 'Store not found'}, 404
 
+    @api_required
     def post(self, name):
         if StoreModel.find_by_name(name):
             return {'message': "A store with name '{}' already exists.".format(name)}, 400
        

user.py (+1, -0)

From: /before-custom-decorator/resources/user.py

To: /after-custom-decorator/resources/user.py

            
            index 759e003..0cc98f4 100644
--- a/tmp/repos/LUS24/example_flask_restful_custom_api_key/before-custom-decorator/resources/user.py
+++ b/tmp/repos/LUS24/example_flask_restful_custom_api_key/after-custom-decorator/resources/user.py
@@ -1,3 +1,4 @@
+from email import parser
 from flask_restful import Resource, reqparse
 from models.user import UserModel
        

security.py (+25, -1)

From: /before-custom-decorator/security.py

To: /after-custom-decorator/security.py

            
            index 53474dd..bae3013 100644
--- a/tmp/repos/LUS24/example_flask_restful_custom_api_key/before-custom-decorator/security.py
+++ b/tmp/repos/LUS24/example_flask_restful_custom_api_key/after-custom-decorator/security.py
@@ -1,5 +1,8 @@
-from models.user import UserModel
+import functools
 from hmac import compare_digest
+from flask import request
+from models.user import UserModel
+from models.device import DeviceModel
 
 
 def authenticate(username, password):
@@ -11,3 +14,24 @@ def authenticate(username, password):
 def identity(payload):
     user_id = payload['identity']
     return UserModel.find_by_id(user_id)
+
+
+def is_valid(api_key):
+    device = DeviceModel.find_by_device_key(api_key)
+    if device and compare_digest(device.device_key, api_key):
+        return True
+
+
+def api_required(func):
+    @functools.wraps(func)
+    def decorator(*args, **kwargs):
+        if request.json:
+            api_key = request.json.get("api_key")
+        else:
+            return {"message": "Please provide an API key"}, 400
+        # Check if API key is correct and valid
+        if request.method == "POST" and is_valid(api_key):
+            return func(*args, **kwargs)
+        else:
+            return {"message": "The provided API key is not valid"}, 403
+    return decorator