├── Question.txt ├── README.md ├── swagger.yaml └── authentication.py /Question.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bondeanikets/Authentication-System/master/Question.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Authentication System 2 | 3 | RESTful API authentication system where we verify if a {user and password} combination matches with the ones stored in a database 4 | 5 | ## Getting Started 6 | 7 | Unzip the folder, and make sure to have following packages and softwares installed: 8 | 9 | ### Prerequisites and Installation 10 | 11 | Flask : ```pip install Flask```. 12 | MongoDB on current machine : https://docs.mongodb.com/manual/installation/. 13 | Flask-PyMongo : ```pip install Flask-PyMongo```. 14 | Postman : https://www.getpostman.com/apps. 15 | 16 | ### Database 17 | 18 | Start MongoDB instance by running ```mongod``` in a terminal, keep it running. 19 | 20 | ### Running the api 21 | 22 | Open new terminal, go into "Authentication System" directory, and run the following command: 23 | 24 | ```python authentication.py``` 25 | 26 | Now you can use Postman to simulate requests. The OpenAPI format specification can be seen as specified below: 27 | 28 | Go to: https://editor.swagger.io/, then clear the left editor. 29 | 30 | Copy content of swagger.yaml from this directory to the link's editor, the right section shows all the possible paths. 31 | Exploit Postman to simulate requests as given in the swagger document, and see the results! 32 | 33 | 34 | ### Reason for chosing MongoDB: 35 | 36 | The current project specification doesn't really exploit the relational structure of relational databases, hence 37 | a non-relational database was an obvious choice. Besides, MongoDB is easy to setup, highly scalable and good at 38 | handling dynamic querries. The scemaless feature of mongoDB is a bonus! 39 | 40 | ### Encryption for passwords: 41 | 42 | Instead of storing raw passwords, they are hashed and then stored. I am using a salt. A salt is a random sequence added to the password string before using the hash function. The salt is used in order to prevent dictionary attacks and rainbow tables attacks. I am sure there are better encryption algorithms, but this seems to be a standard, hence adopted it. 43 | 44 | ## Authors 45 | 46 | * **Aniket Bonde** 47 | -------------------------------------------------------------------------------- /swagger.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | description: "RESTful API authentication system where we verify if a {user and password} combination matches with the ones stored in a database" 4 | version: "1.0.0" 5 | title: "Authentication System" 6 | schemes: 7 | - "https" 8 | paths: 9 | /signup: 10 | post: 11 | tags: 12 | - "signup" 13 | summary: "Add a new credential (username, password) to the database" 14 | description: "" 15 | consumes: 16 | - "application/json" 17 | produces: 18 | - "application/json" 19 | parameters: 20 | - in: "body" 21 | name: "body" 22 | description: "credential object that needs to be added to the store" 23 | required: true 24 | schema: 25 | $ref: "#/definitions/Credential" 26 | responses: 27 | 200: 28 | description: "Signup Data Saved Succesfully" 29 | 30 | 31 | 32 | put: 33 | tags: 34 | - "signup" 35 | summary: "Update an existing credential (password ONLY) to the database 36 | Checks if stored hashed password is same as the password in the body when hashed" 37 | description: "" 38 | consumes: 39 | - "application/json" 40 | produces: 41 | - "application/json" 42 | parameters: 43 | - in: "body" 44 | name: "body" 45 | description: "credential object that needs to be updated to the store" 46 | required: true 47 | schema: 48 | $ref: "#/definitions/CredentialUpdate" 49 | responses: 50 | 200: 51 | description: "Credentials Updated Successfully" 52 | 53 | 54 | delete: 55 | tags: 56 | - "signup" 57 | summary: "Delete an existing credential (username, password) from the database 58 | Checks if stored hashed password is same as the password in the body when hashed" 59 | description: "" 60 | consumes: 61 | - "application/json" 62 | produces: 63 | - "application/json" 64 | parameters: 65 | - in: "body" 66 | name: "body" 67 | description: "credential object that needs to be updated to the store" 68 | required: true 69 | schema: 70 | $ref: "#/definitions/Credential" 71 | responses: 72 | 200: 73 | description: "Credentials Deleted Successfully" 74 | 75 | 76 | 77 | 78 | /login: 79 | post: 80 | tags: 81 | - "login" 82 | summary: "Verify credential (username, password) stored in the database" 83 | description: "" 84 | consumes: 85 | - "application/json" 86 | produces: 87 | - "application/json" 88 | parameters: 89 | - in: "body" 90 | name: "body" 91 | description: "credential object that needs to be added to the store" 92 | required: true 93 | schema: 94 | $ref: "#/definitions/Credential" 95 | responses: 96 | 200: 97 | description: "You are logged in!" 98 | 99 | definitions: 100 | 101 | 102 | Credential: 103 | type: "object" 104 | required: 105 | - "username" 106 | - "password" 107 | properties: 108 | username: 109 | type: "string" 110 | example: "Aniket" 111 | password: 112 | type: "string" 113 | example: "Bonde" 114 | 115 | 116 | CredentialUpdate: 117 | type: "object" 118 | required: 119 | - "username" 120 | - "current_password" 121 | - "new_password" 122 | properties: 123 | username: 124 | type: "string" 125 | example: "Aniket" 126 | current_password: 127 | type: "string" 128 | example: "Bonde" 129 | new_password: 130 | type: "string" 131 | example: "SBonde" 132 | 133 | -------------------------------------------------------------------------------- /authentication.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | import json 3 | import uuid 4 | import hashlib 5 | from flask_pymongo import PyMongo 6 | 7 | app = Flask(__name__) 8 | DB_NAME='signup' 9 | app.config["MONGO_URI"] = "mongodb://localhost:27017/" + DB_NAME 10 | mongo = PyMongo(app) 11 | 12 | @app.route("/") 13 | def main_page(): 14 | return('Welcome to Signup App') 15 | 16 | 17 | @app.route("/login", methods=['POST']) 18 | def login(): 19 | credentials = request.get_json(force=True) 20 | password_check_bool = check_password(get_from_db({'username':credentials['username']})['password'], credentials['password']) 21 | retrieved_data = get_from_db({'username':credentials['username']}) 22 | if password_check_bool and retrieved_data: 23 | return ("You are logged in!") 24 | else: 25 | return ("Incorrect Username or Password") 26 | 27 | @app.route("/signup", methods=['POST']) 28 | def signup_page(): 29 | credentials = request.get_json(force=True) 30 | validate_bool, errors = validate(credentials) 31 | same_existing_username = get_from_db(query={'username':credentials['username']}) 32 | if validate_bool and not same_existing_username: 33 | credentials['password'] = hash_password(credentials['password']) 34 | save_to_db(credentials) 35 | return ('Signup Data Saved Succesfully') 36 | elif same_existing_username: 37 | return ("Same username is already present in the database") 38 | else: 39 | return ("Invalid Signup Data. " + ', '.join(errors)) 40 | 41 | 42 | @app.route("/signup", methods=['PUT']) 43 | def update_signup_data(): 44 | credentials = request.get_json(force=True) 45 | # Checks if stored hashed password is same as the password in the body when hashed 46 | password_check_bool = check_password(get_from_db({'username':credentials['username']})['password'], 47 | credentials['current_password']) 48 | if password_check_bool: 49 | update_data_in_db(credentials) 50 | return ("Credentials Updated Successfully") 51 | else: 52 | return ("Credentials NOT Updated, improper username or password") 53 | 54 | 55 | 56 | @app.route("/signup", methods=['DELETE']) 57 | def delete_signup_data(): 58 | credentials = request.get_json(force=True) 59 | # Checks if stored hashed password is same as the password in the body when hashed 60 | password_check_bool = check_password(get_from_db({'username':credentials['username']})['password'], credentials['password']) 61 | if password_check_bool: 62 | remove_from_db({'username':credentials['username']}) 63 | return ("Credentials Deleted Successfully") 64 | else: 65 | return ("Credentials NOT Deleted, improper username or password") 66 | 67 | 68 | ################################# HELPER FUNCTIONS ##################################### 69 | 70 | def hash_password(password): 71 | # uuid is used to generate a random number 72 | salt = uuid.uuid4().hex 73 | return hashlib.sha256(salt.encode() + password.encode()).hexdigest() + ':' + salt 74 | 75 | def check_password(hashed_password, user_password): 76 | password, salt = hashed_password.split(':') 77 | return password == hashlib.sha256(salt.encode() + user_password.encode()).hexdigest() 78 | 79 | def validate(credentials): 80 | errors = [] 81 | for field in ['username', 'password']: 82 | if field not in credentials: 83 | errors.append(field + ' not present in request data') 84 | return ('username' in credentials and 'password' in credentials and not errors), errors 85 | 86 | 87 | 88 | 89 | ################################# DATABASE FUNCTIONS ##################################### 90 | 91 | def get_from_db(query): 92 | retrieved_data = mongo.db.login_credentials.find_one(query) 93 | return retrieved_data 94 | 95 | def save_to_db(credentials): 96 | mongo.db.login_credentials.insert(credentials) 97 | return 98 | 99 | def update_data_in_db(credentials): 100 | query = {"username": credentials['username']} 101 | update = {"$set": {"password": hash_password(credentials['new_password'])}} 102 | mongo.db.login_credentials.update_one(query, update) 103 | return 104 | 105 | def remove_from_db(credentials): 106 | mongo.db.login_credentials.remove(credentials) 107 | return 108 | 109 | 110 | if __name__ == '__main__': 111 | app.run() 112 | 113 | --------------------------------------------------------------------------------