├── .gitignore ├── 10_blacklisting ├── README.md ├── end │ ├── app.py │ ├── blacklist.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ └── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py └── start │ ├── app.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ └── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py ├── 11_logging_users_out ├── README.md ├── end │ ├── app.py │ ├── blacklist.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ └── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py └── start │ ├── app.py │ ├── blacklist.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ └── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py ├── 12_further_reading └── README.md ├── 1_recap_of_code ├── README.md ├── end │ ├── app.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ ├── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ └── security.py └── start │ ├── app.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ ├── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ └── security.py ├── 2_adding_user_resource_for_testing ├── README.md ├── end │ ├── app.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ ├── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ └── security.py └── start │ ├── app.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ ├── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ └── security.py ├── 3_login_with_flask_jwt_extended ├── README.md ├── end │ ├── app.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ └── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py └── start │ ├── app.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ ├── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ └── security.py ├── 4_adding_claims ├── README.md ├── end │ ├── app.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ └── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py └── start │ ├── app.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ └── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py ├── 5_jwt_optional ├── README.md ├── end │ ├── app.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ └── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py └── start │ ├── app.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ └── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py ├── 7_token_refresh ├── README.md ├── end │ ├── app.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ └── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py └── start │ ├── app.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ └── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py ├── 8_requiring_a_fresh_token ├── README.md ├── end │ ├── app.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ └── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py └── start │ ├── app.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ └── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py ├── 9_customizing_callbacks_and_responses ├── README.md ├── end │ ├── app.py │ ├── db.py │ ├── models │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py │ ├── requirements.txt │ └── resources │ │ ├── __init__.py │ │ ├── item.py │ │ ├── store.py │ │ └── user.py └── start │ ├── app.py │ ├── db.py │ ├── models │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py │ ├── requirements.txt │ └── resources │ ├── __init__.py │ ├── item.py │ ├── store.py │ └── user.py ├── Pipfile ├── Pipfile.lock └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | data.db 2 | .vscode/ 3 | *.pyc 4 | .idea/ 5 | __pycache__/ 6 | venv/ 7 | -------------------------------------------------------------------------------- /10_blacklisting/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added new `blacklist.py` file, with a single set of blacklisted user identities. 4 | * Imported in `app.py`; 5 | * Added new `@jwt.token_in_blacklist_loader` as well. 6 | * Added `app.config['JWT_BLACKLIST_ENABLED'] = True # enable blacklist feature` 7 | * Added `app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access', 'refresh'] # allow blacklisting for access and refresh tokens` -------------------------------------------------------------------------------- /10_blacklisting/end/blacklist.py: -------------------------------------------------------------------------------- 1 | BLACKLIST = {2, 3} -------------------------------------------------------------------------------- /10_blacklisting/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /10_blacklisting/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /10_blacklisting/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /10_blacklisting/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /10_blacklisting/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /10_blacklisting/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /10_blacklisting/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /10_blacklisting/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | fresh_jwt_required, 8 | ) 9 | from models.item import ItemModel 10 | 11 | 12 | class Item(Resource): 13 | parser = reqparse.RequestParser() 14 | parser.add_argument( 15 | "price", type=float, required=True, help="This field cannot be left blank!" 16 | ) 17 | parser.add_argument( 18 | "store_id", type=int, required=True, help="Every item needs a store_id." 19 | ) 20 | 21 | @jwt_required() 22 | def get(self, name): 23 | item = ItemModel.find_by_name(name) 24 | if item: 25 | return item.json() 26 | return {"message": "Item not found"}, 404 27 | 28 | @jwt_required(fresh=True) 29 | def post(self, name): 30 | if ItemModel.find_by_name(name): 31 | return { 32 | "message": "An item with name '{}' already exists.".format(name) 33 | }, 400 34 | 35 | data = Item.parser.parse_args() 36 | 37 | item = ItemModel(name, **data) 38 | 39 | try: 40 | item.save_to_db() 41 | except: 42 | return {"message": "An error occurred inserting the item."}, 500 43 | 44 | return item.json(), 201 45 | 46 | @jwt_required() 47 | def delete(self, name): 48 | claims = get_jwt_claims() 49 | if not claims["is_admin"]: 50 | return {"message": "Admin privilege required."}, 401 51 | 52 | item = ItemModel.find_by_name(name) 53 | if item: 54 | item.delete_from_db() 55 | return {"message": "Item deleted."} 56 | return {"message": "Item not found."}, 404 57 | 58 | def put(self, name): 59 | data = Item.parser.parse_args() 60 | 61 | item = ItemModel.find_by_name(name) 62 | 63 | if item: 64 | item.price = data["price"] 65 | else: 66 | item = ItemModel(name, **data) 67 | 68 | item.save_to_db() 69 | 70 | return item.json() 71 | 72 | 73 | class ItemList(Resource): 74 | @jwt_required(optional=True) 75 | def get(self): 76 | """ 77 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 78 | we return the entire item list. 79 | 80 | Otherwise we just return the item names. 81 | 82 | This could be done with e.g. see orders that have been placed, but not see details about the orders 83 | unless the user has logged in. 84 | """ 85 | user_id = get_jwt_identity() 86 | items = [item.json() for item in ItemModel.find_all()] 87 | if user_id: 88 | return {"items": items}, 200 89 | return { 90 | "items": [item["name"] for item in items], 91 | "message": "More data available if you log in.", 92 | }, 200 93 | -------------------------------------------------------------------------------- /10_blacklisting/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /10_blacklisting/start/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin, TokenRefresh 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | # The following callbacks are used for customizing jwt response/error messages. 38 | # The original ones may not be in a very pretty format (opinionated) 39 | @jwt.expired_token_loader 40 | def expired_token_callback(): 41 | return jsonify({ 42 | 'message': 'The token has expired.', 43 | 'error': 'token_expired' 44 | }), 401 45 | 46 | 47 | @jwt.invalid_token_loader 48 | def invalid_token_callback(error): # we have to keep the argument here, since it's passed in by the caller internally 49 | return jsonify({ 50 | 'message': 'Signature verification failed.', 51 | 'error': 'invalid_token' 52 | }), 401 53 | 54 | 55 | @jwt.unauthorized_loader 56 | def missing_token_callback(error): 57 | return jsonify({ 58 | "description": "Request does not contain an access token.", 59 | 'error': 'authorization_required' 60 | }), 401 61 | 62 | 63 | @jwt.needs_fresh_token_loader 64 | def token_not_fresh_callback(): 65 | return jsonify({ 66 | "description": "The token is not fresh.", 67 | 'error': 'fresh_token_required' 68 | }), 401 69 | 70 | 71 | @jwt.revoked_token_loader 72 | def revoked_token_callback(): 73 | return jsonify({ 74 | "description": "The token has been revoked.", 75 | 'error': 'token_revoked' 76 | }), 401 77 | 78 | api.add_resource(Store, '/store/') 79 | api.add_resource(StoreList, '/stores') 80 | api.add_resource(Item, '/item/') 81 | api.add_resource(ItemList, '/items') 82 | api.add_resource(UserRegister, '/register') 83 | api.add_resource(User, '/user/') 84 | api.add_resource(UserLogin, '/login') 85 | api.add_resource(TokenRefresh, '/refresh') 86 | 87 | if __name__ == '__main__': 88 | db.init_app(app) 89 | app.run(port=5000, debug=True) 90 | -------------------------------------------------------------------------------- /10_blacklisting/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /10_blacklisting/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /10_blacklisting/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /10_blacklisting/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /10_blacklisting/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /10_blacklisting/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /10_blacklisting/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /10_blacklisting/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | fresh_jwt_required, 8 | ) 9 | from models.item import ItemModel 10 | 11 | 12 | class Item(Resource): 13 | parser = reqparse.RequestParser() 14 | parser.add_argument( 15 | "price", type=float, required=True, help="This field cannot be left blank!" 16 | ) 17 | parser.add_argument( 18 | "store_id", type=int, required=True, help="Every item needs a store_id." 19 | ) 20 | 21 | @jwt_required() 22 | def get(self, name): 23 | item = ItemModel.find_by_name(name) 24 | if item: 25 | return item.json() 26 | return {"message": "Item not found"}, 404 27 | 28 | @jwt_required(fresh=True) 29 | def post(self, name): 30 | if ItemModel.find_by_name(name): 31 | return { 32 | "message": "An item with name '{}' already exists.".format(name) 33 | }, 400 34 | 35 | data = Item.parser.parse_args() 36 | 37 | item = ItemModel(name, **data) 38 | 39 | try: 40 | item.save_to_db() 41 | except: 42 | return {"message": "An error occurred inserting the item."}, 500 43 | 44 | return item.json(), 201 45 | 46 | @jwt_required() 47 | def delete(self, name): 48 | claims = get_jwt_claims() 49 | if not claims["is_admin"]: 50 | return {"message": "Admin privilege required."}, 401 51 | 52 | item = ItemModel.find_by_name(name) 53 | if item: 54 | item.delete_from_db() 55 | return {"message": "Item deleted."} 56 | return {"message": "Item not found."}, 404 57 | 58 | def put(self, name): 59 | data = Item.parser.parse_args() 60 | 61 | item = ItemModel.find_by_name(name) 62 | 63 | if item: 64 | item.price = data["price"] 65 | else: 66 | item = ItemModel(name, **data) 67 | 68 | item.save_to_db() 69 | 70 | return item.json() 71 | 72 | 73 | class ItemList(Resource): 74 | @jwt_required(optional=True) 75 | def get(self): 76 | """ 77 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 78 | we return the entire item list. 79 | 80 | Otherwise we just return the item names. 81 | 82 | This could be done with e.g. see orders that have been placed, but not see details about the orders 83 | unless the user has logged in. 84 | """ 85 | user_id = get_jwt_identity() 86 | items = [item.json() for item in ItemModel.find_all()] 87 | if user_id: 88 | return {"items": items}, 200 89 | return { 90 | "items": [item["name"] for item in items], 91 | "message": "More data available if you log in.", 92 | }, 200 93 | -------------------------------------------------------------------------------- /10_blacklisting/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /11_logging_users_out/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added `UserLogout` resource in `user.py`; 4 | * Imported and added to `app.py` 5 | * Modified blacklist loader so it checks the JWT's `'jti'` key instead of the `'identity'`. 6 | * Made `BLACKLIST` initially an empty set. -------------------------------------------------------------------------------- /11_logging_users_out/end/blacklist.py: -------------------------------------------------------------------------------- 1 | BLACKLIST = set() -------------------------------------------------------------------------------- /11_logging_users_out/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /11_logging_users_out/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /11_logging_users_out/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /11_logging_users_out/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /11_logging_users_out/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /11_logging_users_out/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /11_logging_users_out/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /11_logging_users_out/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | fresh_jwt_required, 8 | ) 9 | from models.item import ItemModel 10 | 11 | 12 | class Item(Resource): 13 | parser = reqparse.RequestParser() 14 | parser.add_argument( 15 | "price", type=float, required=True, help="This field cannot be left blank!" 16 | ) 17 | parser.add_argument( 18 | "store_id", type=int, required=True, help="Every item needs a store_id." 19 | ) 20 | 21 | @jwt_required() 22 | def get(self, name): 23 | item = ItemModel.find_by_name(name) 24 | if item: 25 | return item.json() 26 | return {"message": "Item not found"}, 404 27 | 28 | @jwt_required(fresh=True) 29 | def post(self, name): 30 | if ItemModel.find_by_name(name): 31 | return { 32 | "message": "An item with name '{}' already exists.".format(name) 33 | }, 400 34 | 35 | data = Item.parser.parse_args() 36 | 37 | item = ItemModel(name, **data) 38 | 39 | try: 40 | item.save_to_db() 41 | except: 42 | return {"message": "An error occurred inserting the item."}, 500 43 | 44 | return item.json(), 201 45 | 46 | @jwt_required() 47 | def delete(self, name): 48 | claims = get_jwt_claims() 49 | if not claims["is_admin"]: 50 | return {"message": "Admin privilege required."}, 401 51 | 52 | item = ItemModel.find_by_name(name) 53 | if item: 54 | item.delete_from_db() 55 | return {"message": "Item deleted."} 56 | return {"message": "Item not found."}, 404 57 | 58 | def put(self, name): 59 | data = Item.parser.parse_args() 60 | 61 | item = ItemModel.find_by_name(name) 62 | 63 | if item: 64 | item.price = data["price"] 65 | else: 66 | item = ItemModel(name, **data) 67 | 68 | item.save_to_db() 69 | 70 | return item.json() 71 | 72 | 73 | class ItemList(Resource): 74 | @jwt_required(optional=True) 75 | def get(self): 76 | """ 77 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 78 | we return the entire item list. 79 | 80 | Otherwise we just return the item names. 81 | 82 | This could be done with e.g. see orders that have been placed, but not see details about the orders 83 | unless the user has logged in. 84 | """ 85 | user_id = get_jwt_identity() 86 | items = [item.json() for item in ItemModel.find_all()] 87 | if user_id: 88 | return {"items": items}, 200 89 | return { 90 | "items": [item["name"] for item in items], 91 | "message": "More data available if you log in.", 92 | }, 200 93 | -------------------------------------------------------------------------------- /11_logging_users_out/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /11_logging_users_out/start/blacklist.py: -------------------------------------------------------------------------------- 1 | BLACKLIST = {2, 3} -------------------------------------------------------------------------------- /11_logging_users_out/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /11_logging_users_out/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /11_logging_users_out/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /11_logging_users_out/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /11_logging_users_out/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /11_logging_users_out/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /11_logging_users_out/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /11_logging_users_out/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | fresh_jwt_required, 8 | ) 9 | from models.item import ItemModel 10 | 11 | 12 | class Item(Resource): 13 | parser = reqparse.RequestParser() 14 | parser.add_argument( 15 | "price", type=float, required=True, help="This field cannot be left blank!" 16 | ) 17 | parser.add_argument( 18 | "store_id", type=int, required=True, help="Every item needs a store_id." 19 | ) 20 | 21 | @jwt_required() 22 | def get(self, name): 23 | item = ItemModel.find_by_name(name) 24 | if item: 25 | return item.json() 26 | return {"message": "Item not found"}, 404 27 | 28 | @jwt_required(fresh=True) 29 | def post(self, name): 30 | if ItemModel.find_by_name(name): 31 | return { 32 | "message": "An item with name '{}' already exists.".format(name) 33 | }, 400 34 | 35 | data = Item.parser.parse_args() 36 | 37 | item = ItemModel(name, **data) 38 | 39 | try: 40 | item.save_to_db() 41 | except: 42 | return {"message": "An error occurred inserting the item."}, 500 43 | 44 | return item.json(), 201 45 | 46 | @jwt_required() 47 | def delete(self, name): 48 | claims = get_jwt_claims() 49 | if not claims["is_admin"]: 50 | return {"message": "Admin privilege required."}, 401 51 | 52 | item = ItemModel.find_by_name(name) 53 | if item: 54 | item.delete_from_db() 55 | return {"message": "Item deleted."} 56 | return {"message": "Item not found."}, 404 57 | 58 | def put(self, name): 59 | data = Item.parser.parse_args() 60 | 61 | item = ItemModel.find_by_name(name) 62 | 63 | if item: 64 | item.price = data["price"] 65 | else: 66 | item = ItemModel(name, **data) 67 | 68 | item.save_to_db() 69 | 70 | return item.json() 71 | 72 | 73 | class ItemList(Resource): 74 | @jwt_required(optional=True) 75 | def get(self): 76 | """ 77 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 78 | we return the entire item list. 79 | 80 | Otherwise we just return the item names. 81 | 82 | This could be done with e.g. see orders that have been placed, but not see details about the orders 83 | unless the user has logged in. 84 | """ 85 | user_id = get_jwt_identity() 86 | items = [item.json() for item in ItemModel.find_all()] 87 | if user_id: 88 | return {"items": items}, 200 89 | return { 90 | "items": [item["name"] for item in items], 91 | "message": "More data available if you log in.", 92 | }, 200 93 | -------------------------------------------------------------------------------- /11_logging_users_out/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /12_further_reading/README.md: -------------------------------------------------------------------------------- 1 | ## Further reading 2 | 3 | * The official [Flask-JWT-Extended](http://flask-jwt-extended.readthedocs.io/en/latest/index.html) doumentation is a great place to start if you want to learn further about what it can do. 4 | * The [RFC for JWTs](https://tools.ietf.org/html/rfc7519) can also be interesting, to learn more about how JWTs are defined. 5 | -------------------------------------------------------------------------------- /1_recap_of_code/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added requirements.txt 4 | * Using `**data` instead of passing manually in the resources. 5 | * Using list comprehension instead of `list(map())` in the `ItemList` and `StoreList` resources. 6 | * Added more info to `.json()` methods in `ItemModel` and `StoreModel`. 7 | * Modified `ItemListResource` to use `ItemModel.find_all()` instead of using the `query`—as that encapsulates the database interaction in the model instead of exposing it to the resource. 8 | * Modified `StoreListResource` to use `StoreList.find_all()` as well. -------------------------------------------------------------------------------- /1_recap_of_code/end/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt import JWT 4 | 5 | from db import db 6 | from security import authenticate, identity 7 | from resources.user import UserRegister 8 | from resources.item import Item, ItemList 9 | from resources.store import Store, StoreList 10 | 11 | app = Flask(__name__) 12 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 13 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 14 | app.config['PROPAGATE_EXCEPTIONS'] = True 15 | app.secret_key = 'jose' 16 | api = Api(app) 17 | 18 | 19 | @app.before_first_request 20 | def create_tables(): 21 | db.create_all() 22 | 23 | 24 | jwt = JWT(app, authenticate, identity) # /auth 25 | 26 | api.add_resource(Store, '/store/') 27 | api.add_resource(StoreList, '/stores') 28 | api.add_resource(Item, '/item/') 29 | api.add_resource(ItemList, '/items') 30 | api.add_resource(UserRegister, '/register') 31 | 32 | if __name__ == '__main__': 33 | db.init_app(app) 34 | app.run(port=5000, debug=True) 35 | -------------------------------------------------------------------------------- /1_recap_of_code/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /1_recap_of_code/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /1_recap_of_code/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /1_recap_of_code/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /1_recap_of_code/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def save_to_db(self): 16 | db.session.add(self) 17 | db.session.commit() 18 | 19 | @classmethod 20 | def find_by_username(cls, username): 21 | return cls.query.filter_by(username=username).first() 22 | 23 | @classmethod 24 | def find_by_id(cls, _id): 25 | return cls.query.filter_by(id=_id).first() 26 | -------------------------------------------------------------------------------- /1_recap_of_code/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /1_recap_of_code/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /1_recap_of_code/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt import jwt_required 3 | from models.item import ItemModel 4 | 5 | 6 | class Item(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument('price', 9 | type=float, 10 | required=True, 11 | help="This field cannot be left blank!" 12 | ) 13 | parser.add_argument('store_id', 14 | type=int, 15 | required=True, 16 | help="Every item needs a store_id." 17 | ) 18 | 19 | @jwt_required() 20 | def get(self, name): 21 | item = ItemModel.find_by_name(name) 22 | if item: 23 | return item.json() 24 | return {'message': 'Item not found'}, 404 25 | 26 | def post(self, name): 27 | if ItemModel.find_by_name(name): 28 | return {'message': "An item with name '{}' already exists.".format(name)}, 400 29 | 30 | data = Item.parser.parse_args() 31 | 32 | item = ItemModel(name, **data) 33 | 34 | try: 35 | item.save_to_db() 36 | except: 37 | return {"message": "An error occurred inserting the item."}, 500 38 | 39 | return item.json(), 201 40 | 41 | def delete(self, name): 42 | item = ItemModel.find_by_name(name) 43 | if item: 44 | item.delete_from_db() 45 | return {'message': 'Item deleted.'} 46 | return {'message': 'Item not found.'}, 404 47 | 48 | def put(self, name): 49 | data = Item.parser.parse_args() 50 | 51 | item = ItemModel.find_by_name(name) 52 | 53 | if item: 54 | item.price = data['price'] 55 | else: 56 | item = ItemModel(name, **data) 57 | 58 | item.save_to_db() 59 | 60 | return item.json() 61 | 62 | 63 | class ItemList(Resource): 64 | def get(self): 65 | return {'items': [x.json() for x in ItemModel.find_all()]} 66 | -------------------------------------------------------------------------------- /1_recap_of_code/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /1_recap_of_code/end/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from models.user import UserModel 3 | 4 | 5 | class UserRegister(Resource): 6 | parser = reqparse.RequestParser() 7 | parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | def post(self): 19 | data = UserRegister.parser.parse_args() 20 | 21 | if UserModel.find_by_username(data['username']): 22 | return {"message": "A user with that username already exists"}, 400 23 | 24 | user = UserModel(**data) 25 | user.save_to_db() 26 | 27 | return {"message": "User created successfully."}, 201 28 | -------------------------------------------------------------------------------- /1_recap_of_code/end/security.py: -------------------------------------------------------------------------------- 1 | from werkzeug.security import safe_str_cmp 2 | from models.user import UserModel 3 | 4 | 5 | def authenticate(username, password): 6 | user = UserModel.find_by_username(username) 7 | if user and safe_str_cmp(user.password, password): 8 | return user 9 | 10 | 11 | def identity(payload): 12 | user_id = payload['identity'] 13 | return UserModel.find_by_id(user_id) 14 | -------------------------------------------------------------------------------- /1_recap_of_code/start/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt import JWT 4 | 5 | from security import authenticate, identity 6 | from resources.user import UserRegister 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWT(app, authenticate, identity) # /auth 24 | 25 | api.add_resource(Store, '/store/') 26 | api.add_resource(StoreList, '/stores') 27 | api.add_resource(Item, '/item/') 28 | api.add_resource(ItemList, '/items') 29 | api.add_resource(UserRegister, '/register') 30 | 31 | if __name__ == '__main__': 32 | from db import db 33 | db.init_app(app) 34 | app.run(port=5000, debug=True) 35 | -------------------------------------------------------------------------------- /1_recap_of_code/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /1_recap_of_code/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /1_recap_of_code/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return {'name': self.name, 'price': self.price} 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | def save_to_db(self): 27 | db.session.add(self) 28 | db.session.commit() 29 | 30 | def delete_from_db(self): 31 | db.session.delete(self) 32 | db.session.commit() 33 | -------------------------------------------------------------------------------- /1_recap_of_code/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return {'name': self.name, 'items': [item.json() for item in self.items.all()]} 17 | 18 | @classmethod 19 | def find_by_name(cls, name): 20 | return cls.query.filter_by(name=name).first() 21 | 22 | def save_to_db(self): 23 | db.session.add(self) 24 | db.session.commit() 25 | 26 | def delete_from_db(self): 27 | db.session.delete(self) 28 | db.session.commit() 29 | -------------------------------------------------------------------------------- /1_recap_of_code/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def save_to_db(self): 16 | db.session.add(self) 17 | db.session.commit() 18 | 19 | @classmethod 20 | def find_by_username(cls, username): 21 | return cls.query.filter_by(username=username).first() 22 | 23 | @classmethod 24 | def find_by_id(cls, _id): 25 | return cls.query.filter_by(id=_id).first() 26 | -------------------------------------------------------------------------------- /1_recap_of_code/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /1_recap_of_code/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /1_recap_of_code/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt import jwt_required 3 | from models.item import ItemModel 4 | 5 | 6 | class Item(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument('price', 9 | type=float, 10 | required=True, 11 | help="This field cannot be left blank!" 12 | ) 13 | parser.add_argument('store_id', 14 | type=int, 15 | required=True, 16 | help="Every item needs a store_id." 17 | ) 18 | 19 | @jwt_required() 20 | def get(self, name): 21 | item = ItemModel.find_by_name(name) 22 | if item: 23 | return item.json() 24 | return {'message': 'Item not found'}, 404 25 | 26 | def post(self, name): 27 | if ItemModel.find_by_name(name): 28 | return {'message': "An item with name '{}' already exists.".format(name)}, 400 29 | 30 | data = Item.parser.parse_args() 31 | 32 | item = ItemModel(name, **data) 33 | 34 | try: 35 | item.save_to_db() 36 | except: 37 | return {"message": "An error occurred inserting the item."}, 500 38 | 39 | return item.json(), 201 40 | 41 | def delete(self, name): 42 | item = ItemModel.find_by_name(name) 43 | if item: 44 | item.delete_from_db() 45 | return {'message': 'Item deleted.'} 46 | return {'message': 'Item not found.'}, 404 47 | 48 | def put(self, name): 49 | data = Item.parser.parse_args() 50 | 51 | item = ItemModel.find_by_name(name) 52 | 53 | if item: 54 | item.price = data['price'] 55 | else: 56 | item = ItemModel(name, **data) 57 | 58 | item.save_to_db() 59 | 60 | return item.json() 61 | 62 | 63 | class ItemList(Resource): 64 | def get(self): 65 | return {'items': list(map(lambda x: x.json(), ItemModel.query.all()))} 66 | -------------------------------------------------------------------------------- /1_recap_of_code/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': list(map(lambda x: x.json(), StoreModel.query.all()))} 35 | -------------------------------------------------------------------------------- /1_recap_of_code/start/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from models.user import UserModel 3 | 4 | 5 | class UserRegister(Resource): 6 | parser = reqparse.RequestParser() 7 | parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | def post(self): 19 | data = UserRegister.parser.parse_args() 20 | 21 | if UserModel.find_by_username(data['username']): 22 | return {"message": "A user with that username already exists"}, 400 23 | 24 | user = UserModel(data['username'], data['password']) 25 | user.save_to_db() 26 | 27 | return {"message": "User created successfully."}, 201 28 | -------------------------------------------------------------------------------- /1_recap_of_code/start/security.py: -------------------------------------------------------------------------------- 1 | from werkzeug.security import safe_str_cmp 2 | from models.user import UserModel 3 | 4 | 5 | def authenticate(username, password): 6 | user = UserModel.find_by_username(username) 7 | if user and safe_str_cmp(user.password, password): 8 | return user 9 | 10 | 11 | def identity(payload): 12 | user_id = payload['identity'] 13 | return UserModel.find_by_id(user_id) 14 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added `User` resource to retrieve info about users 4 | * Imported in `app.py` 5 | * Added `.json()` method to `UserModel` 6 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt import JWT 4 | 5 | from db import db 6 | from security import authenticate, identity 7 | from resources.user import UserRegister, User 8 | from resources.item import Item, ItemList 9 | from resources.store import Store, StoreList 10 | 11 | app = Flask(__name__) 12 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 13 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 14 | app.config['PROPAGATE_EXCEPTIONS'] = True 15 | app.secret_key = 'jose' 16 | api = Api(app) 17 | 18 | 19 | @app.before_first_request 20 | def create_tables(): 21 | db.create_all() 22 | 23 | 24 | jwt = JWT(app, authenticate, identity) # /auth 25 | 26 | api.add_resource(Store, '/store/') 27 | api.add_resource(StoreList, '/stores') 28 | api.add_resource(Item, '/item/') 29 | api.add_resource(ItemList, '/items') 30 | api.add_resource(UserRegister, '/register') 31 | api.add_resource(User, '/user/') 32 | 33 | if __name__ == '__main__': 34 | db.init_app(app) 35 | app.run(port=5000, debug=True) 36 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt import jwt_required 3 | from models.item import ItemModel 4 | 5 | 6 | class Item(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument('price', 9 | type=float, 10 | required=True, 11 | help="This field cannot be left blank!" 12 | ) 13 | parser.add_argument('store_id', 14 | type=int, 15 | required=True, 16 | help="Every item needs a store_id." 17 | ) 18 | 19 | @jwt_required() 20 | def get(self, name): 21 | item = ItemModel.find_by_name(name) 22 | if item: 23 | return item.json() 24 | return {'message': 'Item not found'}, 404 25 | 26 | def post(self, name): 27 | if ItemModel.find_by_name(name): 28 | return {'message': "An item with name '{}' already exists.".format(name)}, 400 29 | 30 | data = Item.parser.parse_args() 31 | 32 | item = ItemModel(name, **data) 33 | 34 | try: 35 | item.save_to_db() 36 | except: 37 | return {"message": "An error occurred inserting the item."}, 500 38 | 39 | return item.json(), 201 40 | 41 | def delete(self, name): 42 | item = ItemModel.find_by_name(name) 43 | if item: 44 | item.delete_from_db() 45 | return {'message': 'Item deleted.'} 46 | return {'message': 'Item not found.'}, 404 47 | 48 | def put(self, name): 49 | data = Item.parser.parse_args() 50 | 51 | item = ItemModel.find_by_name(name) 52 | 53 | if item: 54 | item.price = data['price'] 55 | else: 56 | item = ItemModel(name, **data) 57 | 58 | item.save_to_db() 59 | 60 | return item.json() 61 | 62 | 63 | class ItemList(Resource): 64 | def get(self): 65 | return {'items': [x.json() for x in ItemModel.find_all()]} 66 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from models.user import UserModel 3 | 4 | 5 | class UserRegister(Resource): 6 | parser = reqparse.RequestParser() 7 | parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | def post(self): 19 | data = UserRegister.parser.parse_args() 20 | 21 | if UserModel.find_by_username(data['username']): 22 | return {"message": "A user with that username already exists"}, 400 23 | 24 | user = UserModel(**data) 25 | user.save_to_db() 26 | 27 | return {"message": "User created successfully."}, 201 28 | 29 | 30 | class User(Resource): 31 | """ 32 | This resource can be useful when testing our Flask app. We may not want to expose it to public users, but for the 33 | sake of demonstration in this course, it can be useful when we are manipulating data regarding the users. 34 | """ 35 | @classmethod 36 | def get(cls, user_id: int): 37 | user = UserModel.find_by_id(user_id) 38 | if not user: 39 | return {'message': 'User Not Found'}, 404 40 | return user.json(), 200 41 | 42 | @classmethod 43 | def delete(cls, user_id: int): 44 | user = UserModel.find_by_id(user_id) 45 | if not user: 46 | return {'message': 'User Not Found'}, 404 47 | user.delete_from_db() 48 | return {'message': 'User deleted.'}, 200 49 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/end/security.py: -------------------------------------------------------------------------------- 1 | from werkzeug.security import safe_str_cmp 2 | from models.user import UserModel 3 | 4 | 5 | def authenticate(username, password): 6 | user = UserModel.find_by_username(username) 7 | if user and safe_str_cmp(user.password, password): 8 | return user 9 | 10 | 11 | def identity(payload): 12 | user_id = payload['identity'] 13 | return UserModel.find_by_id(user_id) 14 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt import JWT 4 | 5 | from db import db 6 | from security import authenticate, identity 7 | from resources.user import UserRegister 8 | from resources.item import Item, ItemList 9 | from resources.store import Store, StoreList 10 | 11 | app = Flask(__name__) 12 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 13 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 14 | app.config['PROPAGATE_EXCEPTIONS'] = True 15 | app.secret_key = 'jose' 16 | api = Api(app) 17 | 18 | 19 | @app.before_first_request 20 | def create_tables(): 21 | db.create_all() 22 | 23 | 24 | jwt = JWT(app, authenticate, identity) # /auth 25 | 26 | api.add_resource(Store, '/store/') 27 | api.add_resource(StoreList, '/stores') 28 | api.add_resource(Item, '/item/') 29 | api.add_resource(ItemList, '/items') 30 | api.add_resource(UserRegister, '/register') 31 | 32 | if __name__ == '__main__': 33 | db.init_app(app) 34 | app.run(port=5000, debug=True) 35 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def save_to_db(self): 16 | db.session.add(self) 17 | db.session.commit() 18 | 19 | @classmethod 20 | def find_by_username(cls, username): 21 | return cls.query.filter_by(username=username).first() 22 | 23 | @classmethod 24 | def find_by_id(cls, _id): 25 | return cls.query.filter_by(id=_id).first() 26 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt import jwt_required 3 | from models.item import ItemModel 4 | 5 | 6 | class Item(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument('price', 9 | type=float, 10 | required=True, 11 | help="This field cannot be left blank!" 12 | ) 13 | parser.add_argument('store_id', 14 | type=int, 15 | required=True, 16 | help="Every item needs a store_id." 17 | ) 18 | 19 | @jwt_required() 20 | def get(self, name): 21 | item = ItemModel.find_by_name(name) 22 | if item: 23 | return item.json() 24 | return {'message': 'Item not found'}, 404 25 | 26 | def post(self, name): 27 | if ItemModel.find_by_name(name): 28 | return {'message': "An item with name '{}' already exists.".format(name)}, 400 29 | 30 | data = Item.parser.parse_args() 31 | 32 | item = ItemModel(name, **data) 33 | 34 | try: 35 | item.save_to_db() 36 | except: 37 | return {"message": "An error occurred inserting the item."}, 500 38 | 39 | return item.json(), 201 40 | 41 | def delete(self, name): 42 | item = ItemModel.find_by_name(name) 43 | if item: 44 | item.delete_from_db() 45 | return {'message': 'Item deleted.'} 46 | return {'message': 'Item not found.'}, 404 47 | 48 | def put(self, name): 49 | data = Item.parser.parse_args() 50 | 51 | item = ItemModel.find_by_name(name) 52 | 53 | if item: 54 | item.price = data['price'] 55 | else: 56 | item = ItemModel(name, **data) 57 | 58 | item.save_to_db() 59 | 60 | return item.json() 61 | 62 | 63 | class ItemList(Resource): 64 | def get(self): 65 | return {'items': [x.json() for x in ItemModel.find_all()]} 66 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from models.user import UserModel 3 | 4 | 5 | class UserRegister(Resource): 6 | parser = reqparse.RequestParser() 7 | parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | def post(self): 19 | data = UserRegister.parser.parse_args() 20 | 21 | if UserModel.find_by_username(data['username']): 22 | return {"message": "A user with that username already exists"}, 400 23 | 24 | user = UserModel(**data) 25 | user.save_to_db() 26 | 27 | return {"message": "User created successfully."}, 201 28 | -------------------------------------------------------------------------------- /2_adding_user_resource_for_testing/start/security.py: -------------------------------------------------------------------------------- 1 | from werkzeug.security import safe_str_cmp 2 | from models.user import UserModel 3 | 4 | 5 | def authenticate(username, password): 6 | user = UserModel.find_by_username(username) 7 | if user and safe_str_cmp(user.password, password): 8 | return user 9 | 10 | 11 | def identity(payload): 12 | user_id = payload['identity'] 13 | return UserModel.find_by_id(user_id) 14 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added `UserLogin` resource; 4 | * Removed parser in each resource, instead making it global to the file and private. 5 | * Added `safe_str_cmp` and password checking in the resource. 6 | * Imported and added it in `app.py` 7 | * Mention `app.config['JWT_SECRET_KEY']` in `app.py` as well 8 | * Removed `security.py` file 9 | * Modified `item.py` since it was using `flask_jwt` 10 | * Changed location of import 11 | * Added `requirements.txt` -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | api.add_resource(Store, '/store/') 26 | api.add_resource(StoreList, '/stores') 27 | api.add_resource(Item, '/item/') 28 | api.add_resource(ItemList, '/items') 29 | api.add_resource(UserRegister, '/register') 30 | api.add_resource(User, '/user/') 31 | api.add_resource(UserLogin, '/login') 32 | 33 | if __name__ == '__main__': 34 | db.init_app(app) 35 | app.run(port=5000, debug=True) 36 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import jwt_required 3 | from models.item import ItemModel 4 | 5 | 6 | class Item(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument( 9 | "price", type=float, required=True, help="This field cannot be left blank!" 10 | ) 11 | parser.add_argument( 12 | "store_id", type=int, required=True, help="Every item needs a store_id." 13 | ) 14 | 15 | @jwt_required() 16 | def get(self, name): 17 | item = ItemModel.find_by_name(name) 18 | if item: 19 | return item.json() 20 | return {"message": "Item not found"}, 404 21 | 22 | def post(self, name): 23 | if ItemModel.find_by_name(name): 24 | return { 25 | "message": "An item with name '{}' already exists.".format(name) 26 | }, 400 27 | 28 | data = Item.parser.parse_args() 29 | 30 | item = ItemModel(name, **data) 31 | 32 | try: 33 | item.save_to_db() 34 | except: 35 | return {"message": "An error occurred inserting the item."}, 500 36 | 37 | return item.json(), 201 38 | 39 | def delete(self, name): 40 | item = ItemModel.find_by_name(name) 41 | if item: 42 | item.delete_from_db() 43 | return {"message": "Item deleted."} 44 | return {"message": "Item not found."}, 404 45 | 46 | def put(self, name): 47 | data = Item.parser.parse_args() 48 | 49 | item = ItemModel.find_by_name(name) 50 | 51 | if item: 52 | item.price = data["price"] 53 | else: 54 | item = ItemModel(name, **data) 55 | 56 | item.save_to_db() 57 | 58 | return item.json() 59 | 60 | 61 | class ItemList(Resource): 62 | def get(self): 63 | return {"items": [x.json() for x in ItemModel.find_all()]} 64 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/end/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from werkzeug.security import safe_str_cmp 3 | from flask_jwt_extended import create_access_token, create_refresh_token 4 | from models.user import UserModel 5 | 6 | _user_parser = reqparse.RequestParser() 7 | _user_parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | _user_parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | 19 | class UserRegister(Resource): 20 | def post(self): 21 | data = _user_parser.parse_args() 22 | 23 | if UserModel.find_by_username(data['username']): 24 | return {"message": "A user with that username already exists"}, 400 25 | 26 | user = UserModel(**data) 27 | user.save_to_db() 28 | 29 | return {"message": "User created successfully."}, 201 30 | 31 | 32 | class User(Resource): 33 | """ 34 | This resource can be useful when testing our Flask app. We may not want to expose it to public users, but for the 35 | sake of demonstration in this course, it can be useful when we are manipulating data regarding the users. 36 | """ 37 | @classmethod 38 | def get(cls, user_id: int): 39 | user = UserModel.find_by_id(user_id) 40 | if not user: 41 | return {'message': 'User Not Found'}, 404 42 | return user.json(), 200 43 | 44 | @classmethod 45 | def delete(cls, user_id: int): 46 | user = UserModel.find_by_id(user_id) 47 | if not user: 48 | return {'message': 'User Not Found'}, 404 49 | user.delete_from_db() 50 | return {'message': 'User deleted.'}, 200 51 | 52 | 53 | class UserLogin(Resource): 54 | def post(self): 55 | data = _user_parser.parse_args() 56 | 57 | user = UserModel.find_by_username(data['username']) 58 | 59 | # this is what the `authenticate()` function did in security.py 60 | if user and safe_str_cmp(user.password, data['password']): 61 | # identity= is what the identity() function did in security.py—now stored in the JWT 62 | access_token = create_access_token(identity=user.id, fresh=True) 63 | refresh_token = create_refresh_token(user.id) 64 | return { 65 | 'access_token': access_token, 66 | 'refresh_token': refresh_token 67 | }, 200 68 | 69 | return {"message": "Invalid Credentials!"}, 401 70 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt import JWT 4 | 5 | from db import db 6 | from security import authenticate, identity 7 | from resources.user import UserRegister, User 8 | from resources.item import Item, ItemList 9 | from resources.store import Store, StoreList 10 | 11 | app = Flask(__name__) 12 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 13 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 14 | app.config['PROPAGATE_EXCEPTIONS'] = True 15 | app.secret_key = 'jose' 16 | api = Api(app) 17 | 18 | 19 | @app.before_first_request 20 | def create_tables(): 21 | db.create_all() 22 | 23 | 24 | jwt = JWT(app, authenticate, identity) # /auth 25 | 26 | api.add_resource(Store, '/store/') 27 | api.add_resource(StoreList, '/stores') 28 | api.add_resource(Item, '/item/') 29 | api.add_resource(ItemList, '/items') 30 | api.add_resource(UserRegister, '/register') 31 | api.add_resource(User, '/user/') 32 | 33 | if __name__ == '__main__': 34 | db.init_app(app) 35 | app.run(port=5000, debug=True) 36 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt import jwt_required 3 | from models.item import ItemModel 4 | 5 | 6 | class Item(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument('price', 9 | type=float, 10 | required=True, 11 | help="This field cannot be left blank!" 12 | ) 13 | parser.add_argument('store_id', 14 | type=int, 15 | required=True, 16 | help="Every item needs a store_id." 17 | ) 18 | 19 | @jwt_required() 20 | def get(self, name): 21 | item = ItemModel.find_by_name(name) 22 | if item: 23 | return item.json() 24 | return {'message': 'Item not found'}, 404 25 | 26 | def post(self, name): 27 | if ItemModel.find_by_name(name): 28 | return {'message': "An item with name '{}' already exists.".format(name)}, 400 29 | 30 | data = Item.parser.parse_args() 31 | 32 | item = ItemModel(name, **data) 33 | 34 | try: 35 | item.save_to_db() 36 | except: 37 | return {"message": "An error occurred inserting the item."}, 500 38 | 39 | return item.json(), 201 40 | 41 | def delete(self, name): 42 | item = ItemModel.find_by_name(name) 43 | if item: 44 | item.delete_from_db() 45 | return {'message': 'Item deleted.'} 46 | return {'message': 'Item not found.'}, 404 47 | 48 | def put(self, name): 49 | data = Item.parser.parse_args() 50 | 51 | item = ItemModel.find_by_name(name) 52 | 53 | if item: 54 | item.price = data['price'] 55 | else: 56 | item = ItemModel(name, **data) 57 | 58 | item.save_to_db() 59 | 60 | return item.json() 61 | 62 | 63 | class ItemList(Resource): 64 | def get(self): 65 | return {'items': [x.json() for x in ItemModel.find_all()]} 66 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from models.user import UserModel 3 | 4 | 5 | class UserRegister(Resource): 6 | parser = reqparse.RequestParser() 7 | parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | def post(self): 19 | data = UserRegister.parser.parse_args() 20 | 21 | if UserModel.find_by_username(data['username']): 22 | return {"message": "A user with that username already exists"}, 400 23 | 24 | user = UserModel(**data) 25 | user.save_to_db() 26 | 27 | return {"message": "User created successfully."}, 201 28 | 29 | 30 | class User(Resource): 31 | """ 32 | This resource can be useful when testing our Flask app. We may not want to expose it to public users, but for the 33 | sake of demonstration in this course, it can be useful when we are manipulating data regarding the users. 34 | """ 35 | @classmethod 36 | def get(cls, user_id: int): 37 | user = UserModel.find_by_id(user_id) 38 | if not user: 39 | return {'message': 'User Not Found'}, 404 40 | return user.json(), 200 41 | 42 | @classmethod 43 | def delete(cls, user_id: int): 44 | user = UserModel.find_by_id(user_id) 45 | if not user: 46 | return {'message': 'User Not Found'}, 404 47 | user.delete_from_db() 48 | return {'message': 'User deleted.'}, 200 49 | -------------------------------------------------------------------------------- /3_login_with_flask_jwt_extended/start/security.py: -------------------------------------------------------------------------------- 1 | from werkzeug.security import safe_str_cmp 2 | from models.user import UserModel 3 | 4 | 5 | def authenticate(username, password): 6 | user = UserModel.find_by_username(username) 7 | if user and safe_str_cmp(user.password, password): 8 | return user 9 | 10 | 11 | def identity(payload): 12 | user_id = payload['identity'] 13 | return UserModel.find_by_id(user_id) 14 | -------------------------------------------------------------------------------- /4_adding_claims/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added claims loader in `app.py`. 4 | * Added claims check in `Item.delete()` so that only admin users can delete items. -------------------------------------------------------------------------------- /4_adding_claims/end/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | api.add_resource(Store, '/store/') 38 | api.add_resource(StoreList, '/stores') 39 | api.add_resource(Item, '/item/') 40 | api.add_resource(ItemList, '/items') 41 | api.add_resource(UserRegister, '/register') 42 | api.add_resource(User, '/user/') 43 | api.add_resource(UserLogin, '/login') 44 | 45 | if __name__ == '__main__': 46 | db.init_app(app) 47 | app.run(port=5000, debug=True) 48 | -------------------------------------------------------------------------------- /4_adding_claims/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /4_adding_claims/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /4_adding_claims/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /4_adding_claims/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /4_adding_claims/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /4_adding_claims/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /4_adding_claims/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /4_adding_claims/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import jwt_required, get_jwt_claims 3 | from models.item import ItemModel 4 | 5 | 6 | class Item(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument( 9 | "price", type=float, required=True, help="This field cannot be left blank!" 10 | ) 11 | parser.add_argument( 12 | "store_id", type=int, required=True, help="Every item needs a store_id." 13 | ) 14 | 15 | @jwt_required() 16 | def get(self, name): 17 | item = ItemModel.find_by_name(name) 18 | if item: 19 | return item.json() 20 | return {"message": "Item not found"}, 404 21 | 22 | def post(self, name): 23 | if ItemModel.find_by_name(name): 24 | return { 25 | "message": "An item with name '{}' already exists.".format(name) 26 | }, 400 27 | 28 | data = Item.parser.parse_args() 29 | 30 | item = ItemModel(name, **data) 31 | 32 | try: 33 | item.save_to_db() 34 | except: 35 | return {"message": "An error occurred inserting the item."}, 500 36 | 37 | return item.json(), 201 38 | 39 | @jwt_required() 40 | def delete(self, name): 41 | claims = get_jwt_claims() 42 | if not claims["is_admin"]: 43 | return {"message": "Admin privilege required."}, 401 44 | 45 | item = ItemModel.find_by_name(name) 46 | if item: 47 | item.delete_from_db() 48 | return {"message": "Item deleted."} 49 | return {"message": "Item not found."}, 404 50 | 51 | def put(self, name): 52 | data = Item.parser.parse_args() 53 | 54 | item = ItemModel.find_by_name(name) 55 | 56 | if item: 57 | item.price = data["price"] 58 | else: 59 | item = ItemModel(name, **data) 60 | 61 | item.save_to_db() 62 | 63 | return item.json() 64 | 65 | 66 | class ItemList(Resource): 67 | def get(self): 68 | return {"items": [x.json() for x in ItemModel.find_all()]} 69 | -------------------------------------------------------------------------------- /4_adding_claims/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /4_adding_claims/end/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from werkzeug.security import safe_str_cmp 3 | from flask_jwt_extended import create_access_token, create_refresh_token 4 | from models.user import UserModel 5 | 6 | _user_parser = reqparse.RequestParser() 7 | _user_parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | _user_parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | 19 | class UserRegister(Resource): 20 | def post(self): 21 | data = _user_parser.parse_args() 22 | 23 | if UserModel.find_by_username(data['username']): 24 | return {"message": "A user with that username already exists"}, 400 25 | 26 | user = UserModel(**data) 27 | user.save_to_db() 28 | 29 | return {"message": "User created successfully."}, 201 30 | 31 | 32 | class User(Resource): 33 | """ 34 | This resource can be useful when testing our Flask app. We may not want to expose it to public users, but for the 35 | sake of demonstration in this course, it can be useful when we are manipulating data regarding the users. 36 | """ 37 | @classmethod 38 | def get(cls, user_id: int): 39 | user = UserModel.find_by_id(user_id) 40 | if not user: 41 | return {'message': 'User Not Found'}, 404 42 | return user.json(), 200 43 | 44 | @classmethod 45 | def delete(cls, user_id: int): 46 | user = UserModel.find_by_id(user_id) 47 | if not user: 48 | return {'message': 'User Not Found'}, 404 49 | user.delete_from_db() 50 | return {'message': 'User deleted.'}, 200 51 | 52 | 53 | class UserLogin(Resource): 54 | def post(self): 55 | data = _user_parser.parse_args() 56 | 57 | user = UserModel.find_by_username(data['username']) 58 | 59 | # this is what the `authenticate()` function did in security.py 60 | if user and safe_str_cmp(user.password, data['password']): 61 | # identity= is what the identity() function did in security.py—now stored in the JWT 62 | access_token = create_access_token(identity=user.id, fresh=True) 63 | refresh_token = create_refresh_token(user.id) 64 | return { 65 | 'access_token': access_token, 66 | 'refresh_token': refresh_token 67 | }, 200 68 | 69 | return {"message": "Invalid Credentials!"}, 401 70 | -------------------------------------------------------------------------------- /4_adding_claims/start/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | api.add_resource(Store, '/store/') 26 | api.add_resource(StoreList, '/stores') 27 | api.add_resource(Item, '/item/') 28 | api.add_resource(ItemList, '/items') 29 | api.add_resource(UserRegister, '/register') 30 | api.add_resource(User, '/user/') 31 | api.add_resource(UserLogin, '/login') 32 | 33 | if __name__ == '__main__': 34 | db.init_app(app) 35 | app.run(port=5000, debug=True) 36 | -------------------------------------------------------------------------------- /4_adding_claims/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /4_adding_claims/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /4_adding_claims/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /4_adding_claims/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /4_adding_claims/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /4_adding_claims/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /4_adding_claims/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /4_adding_claims/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import jwt_required 3 | from models.item import ItemModel 4 | 5 | 6 | class Item(Resource): 7 | parser = reqparse.RequestParser() 8 | parser.add_argument( 9 | "price", type=float, required=True, help="This field cannot be left blank!" 10 | ) 11 | parser.add_argument( 12 | "store_id", type=int, required=True, help="Every item needs a store_id." 13 | ) 14 | 15 | @jwt_required() 16 | def get(self, name): 17 | item = ItemModel.find_by_name(name) 18 | if item: 19 | return item.json() 20 | return {"message": "Item not found"}, 404 21 | 22 | def post(self, name): 23 | if ItemModel.find_by_name(name): 24 | return { 25 | "message": "An item with name '{}' already exists.".format(name) 26 | }, 400 27 | 28 | data = Item.parser.parse_args() 29 | 30 | item = ItemModel(name, **data) 31 | 32 | try: 33 | item.save_to_db() 34 | except: 35 | return {"message": "An error occurred inserting the item."}, 500 36 | 37 | return item.json(), 201 38 | 39 | def delete(self, name): 40 | item = ItemModel.find_by_name(name) 41 | if item: 42 | item.delete_from_db() 43 | return {"message": "Item deleted."} 44 | return {"message": "Item not found."}, 404 45 | 46 | def put(self, name): 47 | data = Item.parser.parse_args() 48 | 49 | item = ItemModel.find_by_name(name) 50 | 51 | if item: 52 | item.price = data["price"] 53 | else: 54 | item = ItemModel(name, **data) 55 | 56 | item.save_to_db() 57 | 58 | return item.json() 59 | 60 | 61 | class ItemList(Resource): 62 | def get(self): 63 | return {"items": [x.json() for x in ItemModel.find_all()]} 64 | -------------------------------------------------------------------------------- /4_adding_claims/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /4_adding_claims/start/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from werkzeug.security import safe_str_cmp 3 | from flask_jwt_extended import create_access_token, create_refresh_token 4 | from models.user import UserModel 5 | 6 | _user_parser = reqparse.RequestParser() 7 | _user_parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | _user_parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | 19 | class UserRegister(Resource): 20 | def post(self): 21 | data = _user_parser.parse_args() 22 | 23 | if UserModel.find_by_username(data['username']): 24 | return {"message": "A user with that username already exists"}, 400 25 | 26 | user = UserModel(**data) 27 | user.save_to_db() 28 | 29 | return {"message": "User created successfully."}, 201 30 | 31 | 32 | class User(Resource): 33 | """ 34 | This resource can be useful when testing our Flask app. We may not want to expose it to public users, but for the 35 | sake of demonstration in this course, it can be useful when we are manipulating data regarding the users. 36 | """ 37 | @classmethod 38 | def get(cls, user_id: int): 39 | user = UserModel.find_by_id(user_id) 40 | if not user: 41 | return {'message': 'User Not Found'}, 404 42 | return user.json(), 200 43 | 44 | @classmethod 45 | def delete(cls, user_id: int): 46 | user = UserModel.find_by_id(user_id) 47 | if not user: 48 | return {'message': 'User Not Found'}, 404 49 | user.delete_from_db() 50 | return {'message': 'User deleted.'}, 200 51 | 52 | 53 | class UserLogin(Resource): 54 | def post(self): 55 | data = _user_parser.parse_args() 56 | 57 | user = UserModel.find_by_username(data['username']) 58 | 59 | # this is what the `authenticate()` function did in security.py 60 | if user and safe_str_cmp(user.password, data['password']): 61 | # identity= is what the identity() function did in security.py—now stored in the JWT 62 | access_token = create_access_token(identity=user.id, fresh=True) 63 | refresh_token = create_refresh_token(user.id) 64 | return { 65 | 'access_token': access_token, 66 | 'refresh_token': refresh_token 67 | }, 200 68 | 69 | return {"message": "Invalid Credentials!"}, 401 70 | -------------------------------------------------------------------------------- /5_jwt_optional/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added `@jwt_required(optional=True)` to `ItemList.get()`. If user is logged in, return full item info. Otherwise, only return item names. 4 | * Can use `get_jwt_identity()` to retrieve the identity saved in the JWT when it was created. -------------------------------------------------------------------------------- /5_jwt_optional/end/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | api.add_resource(Store, '/store/') 38 | api.add_resource(StoreList, '/stores') 39 | api.add_resource(Item, '/item/') 40 | api.add_resource(ItemList, '/items') 41 | api.add_resource(UserRegister, '/register') 42 | api.add_resource(User, '/user/') 43 | api.add_resource(UserLogin, '/login') 44 | 45 | if __name__ == '__main__': 46 | db.init_app(app) 47 | app.run(port=5000, debug=True) 48 | -------------------------------------------------------------------------------- /5_jwt_optional/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /5_jwt_optional/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /5_jwt_optional/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /5_jwt_optional/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /5_jwt_optional/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /5_jwt_optional/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /5_jwt_optional/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /5_jwt_optional/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | ) 8 | from models.item import ItemModel 9 | 10 | 11 | class Item(Resource): 12 | parser = reqparse.RequestParser() 13 | parser.add_argument( 14 | "price", type=float, required=True, help="This field cannot be left blank!" 15 | ) 16 | parser.add_argument( 17 | "store_id", type=int, required=True, help="Every item needs a store_id." 18 | ) 19 | 20 | @jwt_required() 21 | def get(self, name): 22 | item = ItemModel.find_by_name(name) 23 | if item: 24 | return item.json() 25 | return {"message": "Item not found"}, 404 26 | 27 | def post(self, name): 28 | if ItemModel.find_by_name(name): 29 | return { 30 | "message": "An item with name '{}' already exists.".format(name) 31 | }, 400 32 | 33 | data = Item.parser.parse_args() 34 | 35 | item = ItemModel(name, **data) 36 | 37 | try: 38 | item.save_to_db() 39 | except: 40 | return {"message": "An error occurred inserting the item."}, 500 41 | 42 | return item.json(), 201 43 | 44 | @jwt_required() 45 | def delete(self, name): 46 | claims = get_jwt_claims() 47 | if not claims["is_admin"]: 48 | return {"message": "Admin privilege required."}, 401 49 | 50 | item = ItemModel.find_by_name(name) 51 | if item: 52 | item.delete_from_db() 53 | return {"message": "Item deleted."} 54 | return {"message": "Item not found."}, 404 55 | 56 | def put(self, name): 57 | data = Item.parser.parse_args() 58 | 59 | item = ItemModel.find_by_name(name) 60 | 61 | if item: 62 | item.price = data["price"] 63 | else: 64 | item = ItemModel(name, **data) 65 | 66 | item.save_to_db() 67 | 68 | return item.json() 69 | 70 | 71 | class ItemList(Resource): 72 | @jwt_required(optional=True) 73 | def get(self): 74 | """ 75 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 76 | we return the entire item list. 77 | 78 | Otherwise we just return the item names. 79 | 80 | This could be done with e.g. see orders that have been placed, but not see details about the orders 81 | unless the user has logged in. 82 | """ 83 | user_id = get_jwt_identity() 84 | items = [item.json() for item in ItemModel.find_all()] 85 | if user_id: 86 | return {"items": items}, 200 87 | return { 88 | "items": [item["name"] for item in items], 89 | "message": "More data available if you log in.", 90 | }, 200 91 | -------------------------------------------------------------------------------- /5_jwt_optional/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /5_jwt_optional/end/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from werkzeug.security import safe_str_cmp 3 | from flask_jwt_extended import create_access_token, create_refresh_token 4 | from models.user import UserModel 5 | 6 | _user_parser = reqparse.RequestParser() 7 | _user_parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | _user_parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | 19 | class UserRegister(Resource): 20 | def post(self): 21 | data = _user_parser.parse_args() 22 | 23 | if UserModel.find_by_username(data['username']): 24 | return {"message": "A user with that username already exists"}, 400 25 | 26 | user = UserModel(**data) 27 | user.save_to_db() 28 | 29 | return {"message": "User created successfully."}, 201 30 | 31 | 32 | class User(Resource): 33 | """ 34 | This resource can be useful when testing our Flask app. We may not want to expose it to public users, but for the 35 | sake of demonstration in this course, it can be useful when we are manipulating data regarding the users. 36 | """ 37 | @classmethod 38 | def get(cls, user_id: int): 39 | user = UserModel.find_by_id(user_id) 40 | if not user: 41 | return {'message': 'User Not Found'}, 404 42 | return user.json(), 200 43 | 44 | @classmethod 45 | def delete(cls, user_id: int): 46 | user = UserModel.find_by_id(user_id) 47 | if not user: 48 | return {'message': 'User Not Found'}, 404 49 | user.delete_from_db() 50 | return {'message': 'User deleted.'}, 200 51 | 52 | 53 | class UserLogin(Resource): 54 | def post(self): 55 | data = _user_parser.parse_args() 56 | 57 | user = UserModel.find_by_username(data['username']) 58 | 59 | # this is what the `authenticate()` function did in security.py 60 | if user and safe_str_cmp(user.password, data['password']): 61 | # identity= is what the identity() function did in security.py—now stored in the JWT 62 | access_token = create_access_token(identity=user.id, fresh=True) 63 | refresh_token = create_refresh_token(user.id) 64 | return { 65 | 'access_token': access_token, 66 | 'refresh_token': refresh_token 67 | }, 200 68 | 69 | return {"message": "Invalid Credentials!"}, 401 70 | -------------------------------------------------------------------------------- /5_jwt_optional/start/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | api.add_resource(Store, '/store/') 38 | api.add_resource(StoreList, '/stores') 39 | api.add_resource(Item, '/item/') 40 | api.add_resource(ItemList, '/items') 41 | api.add_resource(UserRegister, '/register') 42 | api.add_resource(User, '/user/') 43 | api.add_resource(UserLogin, '/login') 44 | 45 | if __name__ == '__main__': 46 | db.init_app(app) 47 | app.run(port=5000, debug=True) 48 | -------------------------------------------------------------------------------- /5_jwt_optional/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /5_jwt_optional/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /5_jwt_optional/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /5_jwt_optional/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /5_jwt_optional/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /5_jwt_optional/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /5_jwt_optional/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /5_jwt_optional/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | ) 8 | from models.item import ItemModel 9 | 10 | 11 | class Item(Resource): 12 | parser = reqparse.RequestParser() 13 | parser.add_argument( 14 | "price", type=float, required=True, help="This field cannot be left blank!" 15 | ) 16 | parser.add_argument( 17 | "store_id", type=int, required=True, help="Every item needs a store_id." 18 | ) 19 | 20 | @jwt_required() 21 | def get(self, name): 22 | item = ItemModel.find_by_name(name) 23 | if item: 24 | return item.json() 25 | return {"message": "Item not found"}, 404 26 | 27 | def post(self, name): 28 | if ItemModel.find_by_name(name): 29 | return { 30 | "message": "An item with name '{}' already exists.".format(name) 31 | }, 400 32 | 33 | data = Item.parser.parse_args() 34 | 35 | item = ItemModel(name, **data) 36 | 37 | try: 38 | item.save_to_db() 39 | except: 40 | return {"message": "An error occurred inserting the item."}, 500 41 | 42 | return item.json(), 201 43 | 44 | @jwt_required() 45 | def delete(self, name): 46 | claims = get_jwt_claims() 47 | if not claims["is_admin"]: 48 | return {"message": "Admin privilege required."}, 401 49 | 50 | item = ItemModel.find_by_name(name) 51 | if item: 52 | item.delete_from_db() 53 | return {"message": "Item deleted."} 54 | return {"message": "Item not found."}, 404 55 | 56 | def put(self, name): 57 | data = Item.parser.parse_args() 58 | 59 | item = ItemModel.find_by_name(name) 60 | 61 | if item: 62 | item.price = data["price"] 63 | else: 64 | item = ItemModel(name, **data) 65 | 66 | item.save_to_db() 67 | 68 | return item.json() 69 | 70 | 71 | class ItemList(Resource): 72 | @jwt_required(optional=True) 73 | def get(self): 74 | """ 75 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 76 | we return the entire item list. 77 | 78 | Otherwise we just return the item names. 79 | 80 | This could be done with e.g. see orders that have been placed, but not see details about the orders 81 | unless the user has logged in. 82 | """ 83 | user_id = get_jwt_identity() 84 | items = [item.json() for item in ItemModel.find_all()] 85 | if user_id: 86 | return {"items": items}, 200 87 | return { 88 | "items": [item["name"] for item in items], 89 | "message": "More data available if you log in.", 90 | }, 200 91 | -------------------------------------------------------------------------------- /5_jwt_optional/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /5_jwt_optional/start/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from werkzeug.security import safe_str_cmp 3 | from flask_jwt_extended import create_access_token, create_refresh_token 4 | from models.user import UserModel 5 | 6 | _user_parser = reqparse.RequestParser() 7 | _user_parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | _user_parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | 19 | class UserRegister(Resource): 20 | def post(self): 21 | data = _user_parser.parse_args() 22 | 23 | if UserModel.find_by_username(data['username']): 24 | return {"message": "A user with that username already exists"}, 400 25 | 26 | user = UserModel(**data) 27 | user.save_to_db() 28 | 29 | return {"message": "User created successfully."}, 201 30 | 31 | 32 | class User(Resource): 33 | """ 34 | This resource can be useful when testing our Flask app. We may not want to expose it to public users, but for the 35 | sake of demonstration in this course, it can be useful when we are manipulating data regarding the users. 36 | """ 37 | @classmethod 38 | def get(cls, user_id: int): 39 | user = UserModel.find_by_id(user_id) 40 | if not user: 41 | return {'message': 'User Not Found'}, 404 42 | return user.json(), 200 43 | 44 | @classmethod 45 | def delete(cls, user_id: int): 46 | user = UserModel.find_by_id(user_id) 47 | if not user: 48 | return {'message': 'User Not Found'}, 404 49 | user.delete_from_db() 50 | return {'message': 'User deleted.'}, 200 51 | 52 | 53 | class UserLogin(Resource): 54 | def post(self): 55 | data = _user_parser.parse_args() 56 | 57 | user = UserModel.find_by_username(data['username']) 58 | 59 | # this is what the `authenticate()` function did in security.py 60 | if user and safe_str_cmp(user.password, data['password']): 61 | # identity= is what the identity() function did in security.py—now stored in the JWT 62 | access_token = create_access_token(identity=user.id, fresh=True) 63 | refresh_token = create_refresh_token(user.id) 64 | return { 65 | 'access_token': access_token, 66 | 'refresh_token': refresh_token 67 | }, 200 68 | 69 | return {"message": "Invalid Credentials!"}, 401 70 | -------------------------------------------------------------------------------- /7_token_refresh/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added `TokenRefresh` resource. 4 | * Requires `Authorization: Bearer {refresh_token}` as a header—nothing else. 5 | * Added `TokenRefresh` to `app.py`. -------------------------------------------------------------------------------- /7_token_refresh/end/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin, TokenRefresh 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | api.add_resource(Store, '/store/') 38 | api.add_resource(StoreList, '/stores') 39 | api.add_resource(Item, '/item/') 40 | api.add_resource(ItemList, '/items') 41 | api.add_resource(UserRegister, '/register') 42 | api.add_resource(User, '/user/') 43 | api.add_resource(UserLogin, '/login') 44 | api.add_resource(TokenRefresh, '/refresh') 45 | 46 | if __name__ == '__main__': 47 | db.init_app(app) 48 | app.run(port=5000, debug=True) 49 | -------------------------------------------------------------------------------- /7_token_refresh/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /7_token_refresh/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /7_token_refresh/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /7_token_refresh/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /7_token_refresh/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /7_token_refresh/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /7_token_refresh/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /7_token_refresh/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | ) 8 | from models.item import ItemModel 9 | 10 | 11 | class Item(Resource): 12 | parser = reqparse.RequestParser() 13 | parser.add_argument( 14 | "price", type=float, required=True, help="This field cannot be left blank!" 15 | ) 16 | parser.add_argument( 17 | "store_id", type=int, required=True, help="Every item needs a store_id." 18 | ) 19 | 20 | @jwt_required() 21 | def get(self, name): 22 | item = ItemModel.find_by_name(name) 23 | if item: 24 | return item.json() 25 | return {"message": "Item not found"}, 404 26 | 27 | def post(self, name): 28 | if ItemModel.find_by_name(name): 29 | return { 30 | "message": "An item with name '{}' already exists.".format(name) 31 | }, 400 32 | 33 | data = Item.parser.parse_args() 34 | 35 | item = ItemModel(name, **data) 36 | 37 | try: 38 | item.save_to_db() 39 | except: 40 | return {"message": "An error occurred inserting the item."}, 500 41 | 42 | return item.json(), 201 43 | 44 | @jwt_required() 45 | def delete(self, name): 46 | claims = get_jwt_claims() 47 | if not claims["is_admin"]: 48 | return {"message": "Admin privilege required."}, 401 49 | 50 | item = ItemModel.find_by_name(name) 51 | if item: 52 | item.delete_from_db() 53 | return {"message": "Item deleted."} 54 | return {"message": "Item not found."}, 404 55 | 56 | def put(self, name): 57 | data = Item.parser.parse_args() 58 | 59 | item = ItemModel.find_by_name(name) 60 | 61 | if item: 62 | item.price = data["price"] 63 | else: 64 | item = ItemModel(name, **data) 65 | 66 | item.save_to_db() 67 | 68 | return item.json() 69 | 70 | 71 | class ItemList(Resource): 72 | @jwt_required(optional=True) 73 | def get(self): 74 | """ 75 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 76 | we return the entire item list. 77 | 78 | Otherwise we just return the item names. 79 | 80 | This could be done with e.g. see orders that have been placed, but not see details about the orders 81 | unless the user has logged in. 82 | """ 83 | user_id = get_jwt_identity() 84 | items = [item.json() for item in ItemModel.find_all()] 85 | if user_id: 86 | return {"items": items}, 200 87 | return { 88 | "items": [item["name"] for item in items], 89 | "message": "More data available if you log in.", 90 | }, 200 91 | -------------------------------------------------------------------------------- /7_token_refresh/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /7_token_refresh/start/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | api.add_resource(Store, '/store/') 38 | api.add_resource(StoreList, '/stores') 39 | api.add_resource(Item, '/item/') 40 | api.add_resource(ItemList, '/items') 41 | api.add_resource(UserRegister, '/register') 42 | api.add_resource(User, '/user/') 43 | api.add_resource(UserLogin, '/login') 44 | 45 | if __name__ == '__main__': 46 | db.init_app(app) 47 | app.run(port=5000, debug=True) 48 | -------------------------------------------------------------------------------- /7_token_refresh/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /7_token_refresh/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /7_token_refresh/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /7_token_refresh/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /7_token_refresh/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /7_token_refresh/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /7_token_refresh/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /7_token_refresh/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | ) 8 | from models.item import ItemModel 9 | 10 | 11 | class Item(Resource): 12 | parser = reqparse.RequestParser() 13 | parser.add_argument( 14 | "price", type=float, required=True, help="This field cannot be left blank!" 15 | ) 16 | parser.add_argument( 17 | "store_id", type=int, required=True, help="Every item needs a store_id." 18 | ) 19 | 20 | @jwt_required() 21 | def get(self, name): 22 | item = ItemModel.find_by_name(name) 23 | if item: 24 | return item.json() 25 | return {"message": "Item not found"}, 404 26 | 27 | def post(self, name): 28 | if ItemModel.find_by_name(name): 29 | return { 30 | "message": "An item with name '{}' already exists.".format(name) 31 | }, 400 32 | 33 | data = Item.parser.parse_args() 34 | 35 | item = ItemModel(name, **data) 36 | 37 | try: 38 | item.save_to_db() 39 | except: 40 | return {"message": "An error occurred inserting the item."}, 500 41 | 42 | return item.json(), 201 43 | 44 | @jwt_required() 45 | def delete(self, name): 46 | claims = get_jwt_claims() 47 | if not claims["is_admin"]: 48 | return {"message": "Admin privilege required."}, 401 49 | 50 | item = ItemModel.find_by_name(name) 51 | if item: 52 | item.delete_from_db() 53 | return {"message": "Item deleted."} 54 | return {"message": "Item not found."}, 404 55 | 56 | def put(self, name): 57 | data = Item.parser.parse_args() 58 | 59 | item = ItemModel.find_by_name(name) 60 | 61 | if item: 62 | item.price = data["price"] 63 | else: 64 | item = ItemModel(name, **data) 65 | 66 | item.save_to_db() 67 | 68 | return item.json() 69 | 70 | 71 | class ItemList(Resource): 72 | @jwt_required(optional=True) 73 | def get(self): 74 | """ 75 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 76 | we return the entire item list. 77 | 78 | Otherwise we just return the item names. 79 | 80 | This could be done with e.g. see orders that have been placed, but not see details about the orders 81 | unless the user has logged in. 82 | """ 83 | user_id = get_jwt_identity() 84 | items = [item.json() for item in ItemModel.find_all()] 85 | if user_id: 86 | return {"items": items}, 200 87 | return { 88 | "items": [item["name"] for item in items], 89 | "message": "More data available if you log in.", 90 | }, 200 91 | -------------------------------------------------------------------------------- /7_token_refresh/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /7_token_refresh/start/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from werkzeug.security import safe_str_cmp 3 | from flask_jwt_extended import create_access_token, create_refresh_token 4 | from models.user import UserModel 5 | 6 | _user_parser = reqparse.RequestParser() 7 | _user_parser.add_argument('username', 8 | type=str, 9 | required=True, 10 | help="This field cannot be blank." 11 | ) 12 | _user_parser.add_argument('password', 13 | type=str, 14 | required=True, 15 | help="This field cannot be blank." 16 | ) 17 | 18 | 19 | class UserRegister(Resource): 20 | def post(self): 21 | data = _user_parser.parse_args() 22 | 23 | if UserModel.find_by_username(data['username']): 24 | return {"message": "A user with that username already exists"}, 400 25 | 26 | user = UserModel(**data) 27 | user.save_to_db() 28 | 29 | return {"message": "User created successfully."}, 201 30 | 31 | 32 | class User(Resource): 33 | """ 34 | This resource can be useful when testing our Flask app. We may not want to expose it to public users, but for the 35 | sake of demonstration in this course, it can be useful when we are manipulating data regarding the users. 36 | """ 37 | @classmethod 38 | def get(cls, user_id: int): 39 | user = UserModel.find_by_id(user_id) 40 | if not user: 41 | return {'message': 'User Not Found'}, 404 42 | return user.json(), 200 43 | 44 | @classmethod 45 | def delete(cls, user_id: int): 46 | user = UserModel.find_by_id(user_id) 47 | if not user: 48 | return {'message': 'User Not Found'}, 404 49 | user.delete_from_db() 50 | return {'message': 'User deleted.'}, 200 51 | 52 | 53 | class UserLogin(Resource): 54 | def post(self): 55 | data = _user_parser.parse_args() 56 | 57 | user = UserModel.find_by_username(data['username']) 58 | 59 | # this is what the `authenticate()` function did in security.py 60 | if user and safe_str_cmp(user.password, data['password']): 61 | # identity= is what the identity() function did in security.py—now stored in the JWT 62 | access_token = create_access_token(identity=user.id, fresh=True) 63 | refresh_token = create_refresh_token(user.id) 64 | return { 65 | 'access_token': access_token, 66 | 'refresh_token': refresh_token 67 | }, 200 68 | 69 | return {"message": "Invalid Credentials!"}, 401 70 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added `@jwt_required(fresh=True)` to `Item.post()`. This means that the JWT provided must be fresh (generated with username/password) instead of having been refreshed. 4 | * Many places do this e.g. if you want to delete your account or something important. 5 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin, TokenRefresh 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | api.add_resource(Store, '/store/') 38 | api.add_resource(StoreList, '/stores') 39 | api.add_resource(Item, '/item/') 40 | api.add_resource(ItemList, '/items') 41 | api.add_resource(UserRegister, '/register') 42 | api.add_resource(User, '/user/') 43 | api.add_resource(UserLogin, '/login') 44 | api.add_resource(TokenRefresh, '/refresh') 45 | 46 | if __name__ == '__main__': 47 | db.init_app(app) 48 | app.run(port=5000, debug=True) 49 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | fresh_jwt_required, 8 | ) 9 | from models.item import ItemModel 10 | 11 | 12 | class Item(Resource): 13 | parser = reqparse.RequestParser() 14 | parser.add_argument( 15 | "price", type=float, required=True, help="This field cannot be left blank!" 16 | ) 17 | parser.add_argument( 18 | "store_id", type=int, required=True, help="Every item needs a store_id." 19 | ) 20 | 21 | @jwt_required() 22 | def get(self, name): 23 | item = ItemModel.find_by_name(name) 24 | if item: 25 | return item.json() 26 | return {"message": "Item not found"}, 404 27 | 28 | @jwt_required(fresh=True) 29 | def post(self, name): 30 | if ItemModel.find_by_name(name): 31 | return { 32 | "message": "An item with name '{}' already exists.".format(name) 33 | }, 400 34 | 35 | data = Item.parser.parse_args() 36 | 37 | item = ItemModel(name, **data) 38 | 39 | try: 40 | item.save_to_db() 41 | except: 42 | return {"message": "An error occurred inserting the item."}, 500 43 | 44 | return item.json(), 201 45 | 46 | @jwt_required() 47 | def delete(self, name): 48 | claims = get_jwt_claims() 49 | if not claims["is_admin"]: 50 | return {"message": "Admin privilege required."}, 401 51 | 52 | item = ItemModel.find_by_name(name) 53 | if item: 54 | item.delete_from_db() 55 | return {"message": "Item deleted."} 56 | return {"message": "Item not found."}, 404 57 | 58 | def put(self, name): 59 | data = Item.parser.parse_args() 60 | 61 | item = ItemModel.find_by_name(name) 62 | 63 | if item: 64 | item.price = data["price"] 65 | else: 66 | item = ItemModel(name, **data) 67 | 68 | item.save_to_db() 69 | 70 | return item.json() 71 | 72 | 73 | class ItemList(Resource): 74 | @jwt_required(optional=True) 75 | def get(self): 76 | """ 77 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 78 | we return the entire item list. 79 | 80 | Otherwise we just return the item names. 81 | 82 | This could be done with e.g. see orders that have been placed, but not see details about the orders 83 | unless the user has logged in. 84 | """ 85 | user_id = get_jwt_identity() 86 | items = [item.json() for item in ItemModel.find_all()] 87 | if user_id: 88 | return {"items": items}, 200 89 | return { 90 | "items": [item["name"] for item in items], 91 | "message": "More data available if you log in.", 92 | }, 200 93 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin, TokenRefresh 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | api.add_resource(Store, '/store/') 38 | api.add_resource(StoreList, '/stores') 39 | api.add_resource(Item, '/item/') 40 | api.add_resource(ItemList, '/items') 41 | api.add_resource(UserRegister, '/register') 42 | api.add_resource(User, '/user/') 43 | api.add_resource(UserLogin, '/login') 44 | api.add_resource(TokenRefresh, '/refresh') 45 | 46 | if __name__ == '__main__': 47 | db.init_app(app) 48 | app.run(port=5000, debug=True) 49 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | ) 8 | from models.item import ItemModel 9 | 10 | 11 | class Item(Resource): 12 | parser = reqparse.RequestParser() 13 | parser.add_argument( 14 | "price", type=float, required=True, help="This field cannot be left blank!" 15 | ) 16 | parser.add_argument( 17 | "store_id", type=int, required=True, help="Every item needs a store_id." 18 | ) 19 | 20 | @jwt_required() 21 | def get(self, name): 22 | item = ItemModel.find_by_name(name) 23 | if item: 24 | return item.json() 25 | return {"message": "Item not found"}, 404 26 | 27 | def post(self, name): 28 | if ItemModel.find_by_name(name): 29 | return { 30 | "message": "An item with name '{}' already exists.".format(name) 31 | }, 400 32 | 33 | data = Item.parser.parse_args() 34 | 35 | item = ItemModel(name, **data) 36 | 37 | try: 38 | item.save_to_db() 39 | except: 40 | return {"message": "An error occurred inserting the item."}, 500 41 | 42 | return item.json(), 201 43 | 44 | @jwt_required() 45 | def delete(self, name): 46 | claims = get_jwt_claims() 47 | if not claims["is_admin"]: 48 | return {"message": "Admin privilege required."}, 401 49 | 50 | item = ItemModel.find_by_name(name) 51 | if item: 52 | item.delete_from_db() 53 | return {"message": "Item deleted."} 54 | return {"message": "Item not found."}, 404 55 | 56 | def put(self, name): 57 | data = Item.parser.parse_args() 58 | 59 | item = ItemModel.find_by_name(name) 60 | 61 | if item: 62 | item.price = data["price"] 63 | else: 64 | item = ItemModel(name, **data) 65 | 66 | item.save_to_db() 67 | 68 | return item.json() 69 | 70 | 71 | class ItemList(Resource): 72 | @jwt_required(optional=True) 73 | def get(self): 74 | """ 75 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 76 | we return the entire item list. 77 | 78 | Otherwise we just return the item names. 79 | 80 | This could be done with e.g. see orders that have been placed, but not see details about the orders 81 | unless the user has logged in. 82 | """ 83 | user_id = get_jwt_identity() 84 | items = [item.json() for item in ItemModel.find_all()] 85 | if user_id: 86 | return {"items": items}, 200 87 | return { 88 | "items": [item["name"] for item in items], 89 | "message": "More data available if you log in.", 90 | }, 200 91 | -------------------------------------------------------------------------------- /8_requiring_a_fresh_token/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/README.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | * Added configuration to `app.py` on JWT. 4 | * `@jwt.expired_token_loader`, for when the provided access token is valid but expired. 5 | * `@jwt.invalid_token_loader`, for when the provided token is invalid. 6 | * `@jwt.unauthorized_loader`, for when a token isn't present. 7 | * `@jwt.needs_fresh_token_loader`, for when the request needs a fresh token but it had a non-fresh token. 8 | * `@jwt.revoked_token_loader`, for when a token has been revoked. -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin, TokenRefresh 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | # The following callbacks are used for customizing jwt response/error messages. 38 | # The original ones may not be in a very pretty format (opinionated) 39 | @jwt.expired_token_loader 40 | def expired_token_callback(): 41 | return jsonify({ 42 | 'description': 'The token has expired.', 43 | 'error': 'token_expired' 44 | }), 401 45 | 46 | 47 | @jwt.invalid_token_loader 48 | def invalid_token_callback(error): # we have to keep the argument here, since it's passed in by the caller internally 49 | return jsonify({ 50 | 'description': 'Signature verification failed.', 51 | 'error': 'invalid_token' 52 | }), 401 53 | 54 | 55 | @jwt.unauthorized_loader 56 | def missing_token_callback(error): 57 | return jsonify({ 58 | 'description': 'Request does not contain an access token.', 59 | 'error': 'authorization_required' 60 | }), 401 61 | 62 | 63 | @jwt.needs_fresh_token_loader 64 | def token_not_fresh_callback(): 65 | return jsonify({ 66 | 'description': 'The token is not fresh.', 67 | 'error': 'fresh_token_required' 68 | }), 401 69 | 70 | 71 | @jwt.revoked_token_loader 72 | def revoked_token_callback(): 73 | return jsonify({ 74 | 'description': 'The token has been revoked.', 75 | 'error': 'token_revoked' 76 | }), 401 77 | 78 | api.add_resource(Store, '/store/') 79 | api.add_resource(StoreList, '/stores') 80 | api.add_resource(Item, '/item/') 81 | api.add_resource(ItemList, '/items') 82 | api.add_resource(UserRegister, '/register') 83 | api.add_resource(User, '/user/') 84 | api.add_resource(UserLogin, '/login') 85 | api.add_resource(TokenRefresh, '/refresh') 86 | 87 | if __name__ == '__main__': 88 | db.init_app(app) 89 | app.run(port=5000, debug=True) 90 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | fresh_jwt_required, 8 | ) 9 | from models.item import ItemModel 10 | 11 | 12 | class Item(Resource): 13 | parser = reqparse.RequestParser() 14 | parser.add_argument( 15 | "price", type=float, required=True, help="This field cannot be left blank!" 16 | ) 17 | parser.add_argument( 18 | "store_id", type=int, required=True, help="Every item needs a store_id." 19 | ) 20 | 21 | @jwt_required() 22 | def get(self, name): 23 | item = ItemModel.find_by_name(name) 24 | if item: 25 | return item.json() 26 | return {"message": "Item not found"}, 404 27 | 28 | @jwt_required(fresh=True) 29 | def post(self, name): 30 | if ItemModel.find_by_name(name): 31 | return { 32 | "message": "An item with name '{}' already exists.".format(name) 33 | }, 400 34 | 35 | data = Item.parser.parse_args() 36 | 37 | item = ItemModel(name, **data) 38 | 39 | try: 40 | item.save_to_db() 41 | except: 42 | return {"message": "An error occurred inserting the item."}, 500 43 | 44 | return item.json(), 201 45 | 46 | @jwt_required() 47 | def delete(self, name): 48 | claims = get_jwt_claims() 49 | if not claims["is_admin"]: 50 | return {"message": "Admin privilege required."}, 401 51 | 52 | item = ItemModel.find_by_name(name) 53 | if item: 54 | item.delete_from_db() 55 | return {"message": "Item deleted."} 56 | return {"message": "Item not found."}, 404 57 | 58 | def put(self, name): 59 | data = Item.parser.parse_args() 60 | 61 | item = ItemModel.find_by_name(name) 62 | 63 | if item: 64 | item.price = data["price"] 65 | else: 66 | item = ItemModel(name, **data) 67 | 68 | item.save_to_db() 69 | 70 | return item.json() 71 | 72 | 73 | class ItemList(Resource): 74 | @jwt_required(optional=True) 75 | def get(self): 76 | """ 77 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 78 | we return the entire item list. 79 | 80 | Otherwise we just return the item names. 81 | 82 | This could be done with e.g. see orders that have been placed, but not see details about the orders 83 | unless the user has logged in. 84 | """ 85 | user_id = get_jwt_identity() 86 | items = [item.json() for item in ItemModel.find_all()] 87 | if user_id: 88 | return {"items": items}, 200 89 | return { 90 | "items": [item["name"] for item in items], 91 | "message": "More data available if you log in.", 92 | }, 200 93 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/end/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_restful import Api 3 | from flask_jwt_extended import JWTManager 4 | 5 | from db import db 6 | from resources.user import UserRegister, User, UserLogin, TokenRefresh 7 | from resources.item import Item, ItemList 8 | from resources.store import Store, StoreList 9 | 10 | app = Flask(__name__) 11 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db' 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['PROPAGATE_EXCEPTIONS'] = True 14 | app.secret_key = 'jose' # could do app.config['JWT_SECRET_KEY'] if we prefer 15 | api = Api(app) 16 | 17 | 18 | @app.before_first_request 19 | def create_tables(): 20 | db.create_all() 21 | 22 | 23 | jwt = JWTManager(app) 24 | 25 | """ 26 | `claims` are data we choose to attach to each jwt payload 27 | and for each jwt protected endpoint, we can retrieve these claims via `get_jwt_claims()` 28 | one possible use case for claims are access level control, which is shown below. 29 | """ 30 | @jwt.user_claims_loader 31 | def add_claims_to_jwt(identity): # Remember identity is what we define when creating the access token 32 | if identity == 1: # instead of hard-coding, we should read from a config file or database to get a list of admins instead 33 | return {'is_admin': True} 34 | return {'is_admin': False} 35 | 36 | 37 | api.add_resource(Store, '/store/') 38 | api.add_resource(StoreList, '/stores') 39 | api.add_resource(Item, '/item/') 40 | api.add_resource(ItemList, '/items') 41 | api.add_resource(UserRegister, '/register') 42 | api.add_resource(User, '/user/') 43 | api.add_resource(UserLogin, '/login') 44 | api.add_resource(TokenRefresh, '/refresh') 45 | 46 | if __name__ == '__main__': 47 | db.init_app(app) 48 | app.run(port=5000, debug=True) 49 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/models/item.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class ItemModel(db.Model): 5 | __tablename__ = 'items' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | price = db.Column(db.Float(precision=2)) 10 | 11 | store_id = db.Column(db.Integer, db.ForeignKey('stores.id')) 12 | store = db.relationship('StoreModel') 13 | 14 | def __init__(self, name, price, store_id): 15 | self.name = name 16 | self.price = price 17 | self.store_id = store_id 18 | 19 | def json(self): 20 | return { 21 | 'id': self.id, 22 | 'name': self.name, 23 | 'price': self.price, 24 | 'store_id': self.store_id 25 | } 26 | 27 | @classmethod 28 | def find_by_name(cls, name): 29 | return cls.query.filter_by(name=name).first() 30 | 31 | @classmethod 32 | def find_all(cls): 33 | return cls.query.all() 34 | 35 | def save_to_db(self): 36 | db.session.add(self) 37 | db.session.commit() 38 | 39 | def delete_from_db(self): 40 | db.session.delete(self) 41 | db.session.commit() 42 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/models/store.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class StoreModel(db.Model): 5 | __tablename__ = 'stores' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(80)) 9 | 10 | items = db.relationship('ItemModel', lazy='dynamic') 11 | 12 | def __init__(self, name): 13 | self.name = name 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'name': self.name, 19 | 'items': [item.json() for item in self.items.all()] 20 | } 21 | 22 | @classmethod 23 | def find_by_name(cls, name): 24 | return cls.query.filter_by(name=name).first() 25 | 26 | @classmethod 27 | def find_all(cls): 28 | return cls.query.all() 29 | 30 | def save_to_db(self): 31 | db.session.add(self) 32 | db.session.commit() 33 | 34 | def delete_from_db(self): 35 | db.session.delete(self) 36 | db.session.commit() 37 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/models/user.py: -------------------------------------------------------------------------------- 1 | from db import db 2 | 3 | 4 | class UserModel(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True) 8 | username = db.Column(db.String(80)) 9 | password = db.Column(db.String(80)) 10 | 11 | def __init__(self, username, password): 12 | self.username = username 13 | self.password = password 14 | 15 | def json(self): 16 | return { 17 | 'id': self.id, 18 | 'username': self.username 19 | } 20 | 21 | def save_to_db(self): 22 | db.session.add(self) 23 | db.session.commit() 24 | 25 | def delete_from_db(self): 26 | db.session.delete(self) 27 | db.session.commit() 28 | 29 | @classmethod 30 | def find_by_username(cls, username): 31 | return cls.query.filter_by(username=username).first() 32 | 33 | @classmethod 34 | def find_by_id(cls, _id): 35 | return cls.query.filter_by(id=_id).first() 36 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/resources/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/resources/item.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource, reqparse 2 | from flask_jwt_extended import ( 3 | jwt_required, 4 | get_jwt_claims, 5 | get_jwt_identity, 6 | jwt_optional, 7 | fresh_jwt_required, 8 | ) 9 | from models.item import ItemModel 10 | 11 | 12 | class Item(Resource): 13 | parser = reqparse.RequestParser() 14 | parser.add_argument( 15 | "price", type=float, required=True, help="This field cannot be left blank!" 16 | ) 17 | parser.add_argument( 18 | "store_id", type=int, required=True, help="Every item needs a store_id." 19 | ) 20 | 21 | @jwt_required() 22 | def get(self, name): 23 | item = ItemModel.find_by_name(name) 24 | if item: 25 | return item.json() 26 | return {"message": "Item not found"}, 404 27 | 28 | @jwt_required(fresh=True) 29 | def post(self, name): 30 | if ItemModel.find_by_name(name): 31 | return { 32 | "message": "An item with name '{}' already exists.".format(name) 33 | }, 400 34 | 35 | data = Item.parser.parse_args() 36 | 37 | item = ItemModel(name, **data) 38 | 39 | try: 40 | item.save_to_db() 41 | except: 42 | return {"message": "An error occurred inserting the item."}, 500 43 | 44 | return item.json(), 201 45 | 46 | @jwt_required() 47 | def delete(self, name): 48 | claims = get_jwt_claims() 49 | if not claims["is_admin"]: 50 | return {"message": "Admin privilege required."}, 401 51 | 52 | item = ItemModel.find_by_name(name) 53 | if item: 54 | item.delete_from_db() 55 | return {"message": "Item deleted."} 56 | return {"message": "Item not found."}, 404 57 | 58 | def put(self, name): 59 | data = Item.parser.parse_args() 60 | 61 | item = ItemModel.find_by_name(name) 62 | 63 | if item: 64 | item.price = data["price"] 65 | else: 66 | item = ItemModel(name, **data) 67 | 68 | item.save_to_db() 69 | 70 | return item.json() 71 | 72 | 73 | class ItemList(Resource): 74 | @jwt_required(optional=True) 75 | def get(self): 76 | """ 77 | Here we get the JWT identity, and then if the user is logged in (we were able to get an identity) 78 | we return the entire item list. 79 | 80 | Otherwise we just return the item names. 81 | 82 | This could be done with e.g. see orders that have been placed, but not see details about the orders 83 | unless the user has logged in. 84 | """ 85 | user_id = get_jwt_identity() 86 | items = [item.json() for item in ItemModel.find_all()] 87 | if user_id: 88 | return {"items": items}, 200 89 | return { 90 | "items": [item["name"] for item in items], 91 | "message": "More data available if you log in.", 92 | }, 200 93 | -------------------------------------------------------------------------------- /9_customizing_callbacks_and_responses/start/resources/store.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from models.store import StoreModel 3 | 4 | 5 | class Store(Resource): 6 | def get(self, name): 7 | store = StoreModel.find_by_name(name) 8 | if store: 9 | return store.json() 10 | return {'message': 'Store not found'}, 404 11 | 12 | def post(self, name): 13 | if StoreModel.find_by_name(name): 14 | return {'message': "A store with name '{}' already exists.".format(name)}, 400 15 | 16 | store = StoreModel(name) 17 | try: 18 | store.save_to_db() 19 | except: 20 | return {"message": "An error occurred creating the store."}, 500 21 | 22 | return store.json(), 201 23 | 24 | def delete(self, name): 25 | store = StoreModel.find_by_name(name) 26 | if store: 27 | store.delete_from_db() 28 | 29 | return {'message': 'Store deleted'} 30 | 31 | 32 | class StoreList(Resource): 33 | def get(self): 34 | return {'stores': [x.json() for x in StoreModel.find_all()]} 35 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | flask-jwt-extended = "*" 11 | flask-sqlalchemy = "*" 12 | flask-restful = "*" 13 | flask-jwt = "*" 14 | 15 | 16 | [dev-packages] 17 | 18 | 19 | 20 | [requires] 21 | 22 | python_version = "3.6" 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JWT-Extended 2 | Flask-RESTful 3 | Flask-SQLAlchemy 4 | --------------------------------------------------------------------------------