Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upd: Email service for forgot password added #15

Merged
merged 2 commits into from
Dec 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# IDE specific config files
.vscode/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# omg-frames-api
# IWasAt

This is the backend of the [OMG Frames](https://github.com/dsc-x/omg-frames) having the register/login routes, and other related ones.

Link to the front-end repo: [omg-frames](https://github.com/dsc-x/omg-frames)

Backend is running at: [http://104.236.25.178/api/v1](http://104.236.25.178/api/v1)
Backend is running at: [https://api.iwasat.events/api/v1](https://api.iwasat.events/api/v1/)

All the API endpoints are prefixed by `/api/v1` e.g `http://104.236.25.178/api/v1/login`
All the API endpoints are prefixed by `/api/v1` e.g `https://api.iwasat.events/api/v1/login`

## Technologies used

Expand All @@ -18,7 +18,7 @@ All the API endpoints are prefixed by `/api/v1` e.g `http://104.236.25.178/api/v

## API Endpoints

For API documentation go to [http://104.236.25.178/apidocs/](http://104.236.25.178/apidocs/).
For API documentation go to [https://api.iwasat.events/apidocs/](https://api.iwasat.events/apidocs/).

## Development

Expand Down Expand Up @@ -51,21 +51,22 @@ This will start the local server in port 5000.
## Project structure

```
omg-frames-api
.
├── app
│   ├── db.py
│   ├── __init__.py
│   └── routes.py
├── config.py
├── docs
│   ├── getframes.yml
│   ├── login.yml
│   ├── postframes.yml
│   └── register.yml
├── README.md
├── requirements.txt
├── sample.env
└── server.py
omg-frames-api
├── app
│   ├── db.py
│   ├── __init__.py
│   └── routes.py
├── config.py
├── docs
│   ├── deleteframes.yml
│   ├── getframes.yml
│   ├── login.yml
│   ├── postframes.yml
│   ├── register.yml
│   └── updateframes.yml
├── README.md
├── requirements.txt
├── sample.env
└── server.py
```

8 changes: 4 additions & 4 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from app.db import Db
from flasgger import Swagger
from flask_cors import CORS
from flask_mail import Mail

template = {
"swagger": "2.0",
Expand All @@ -19,18 +20,17 @@
"http",
"https"
],
'securityDefinitions': {
'basicAuth': { 'type': 'apiKey', 'name': 'Authorization', 'in': 'header'}
'securityDefinitions': {
'basicAuth': {'type': 'apiKey', 'name': 'Authorization', 'in': 'header'}
}
}


app = Flask(__name__)
CORS(app)
app.config.from_object(Config)
mail = Mail(app)

swagger = Swagger(app, template=template)

from app import routes


84 changes: 37 additions & 47 deletions app/db.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,7 @@
from config import Config, FirebaseConfig
from config import FirebaseConfig
from passlib.hash import pbkdf2_sha256
from utils import Utils
import pyrebase
import jwt
import datetime


def encode_auth_token(user_id):
try:
payload = {
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1, minutes=0),
'iat': datetime.datetime.utcnow(),
'id': user_id
}
return jwt.encode(
payload,
Config.SECRET_KEY,
algorithm='HS256'
)
except Exception as e:
return e


def decode_auth_token(auth_token):
try:
payload = jwt.decode(auth_token, Config.SECRET_KEY)
return payload['id']
except jwt.ExpiredSignatureError:
print('ERROR: Signature expired. Please log in again.')
return None
except jwt.InvalidTokenError:
print('ERROR: Invalid token. Please log in again.')
return None


class Db:
Expand Down Expand Up @@ -59,7 +30,7 @@ def add_participants(db, userDetails):
print(' * Participant added to db')
return True
except Exception as e:
print('ERROR: <add_participants>' , e)
print('ERROR: <add_participants>', e)
return False

@staticmethod
Expand All @@ -70,7 +41,7 @@ def check_valid_details(db, userDetails):
assert len(userDetails["password"]) > 6

participants = db.child('participants').get().val()
if participants == None:
if participants is None:
# no participant data
return True
for user_id in participants:
Expand Down Expand Up @@ -99,7 +70,7 @@ def authorise_participants(db, userDetails):
@staticmethod
def get_token(db, user_id):
try:
token = encode_auth_token(user_id)
token = Utils.encode_auth_token(user_id)
return token.decode('UTF-8')
except Exception as e:
print('ERROR:', e)
Expand All @@ -108,10 +79,10 @@ def get_token(db, user_id):
@staticmethod
def save_frame(db, token, frame):
try:
user_id = decode_auth_token(token)
if user_id!= None:
user_id = Utils.decode_auth_token(token)
if user_id is not None:
frame_id = db.child('participants').child(user_id).child('frames').push(frame)
frame_obj={
frame_obj = {
"frame_data": frame,
"frame_id": frame_id["name"]
}
Expand All @@ -127,12 +98,12 @@ def save_frame(db, token, frame):
@staticmethod
def get_frames(db, token):
try:
user_id = decode_auth_token(token)
if user_id != None:
user_id = Utils.decode_auth_token(token)
if user_id is not None:
frames = db.child('participants').child(user_id).child('frames').get().val()
frame_arr = []
if frames!= None:
frame_arr = [{"frame_id":fid, "frame_data":frames[fid]} for fid in frames]
if frames is not None:
frame_arr = [{"frame_id": fid, "frame_data": frames[fid]} for fid in frames]
return frame_arr
else:
print('ERROR: Token Value is None')
Expand All @@ -144,8 +115,8 @@ def get_frames(db, token):
@staticmethod
def delete_frames(db, token, frame_id):
try:
user_id = decode_auth_token(token)
if user_id != None:
user_id = Utils.decode_auth_token(token)
if user_id is not None:
db.child('participants').child(user_id).child('frames').child(frame_id).remove()
return True
else:
Expand All @@ -157,12 +128,31 @@ def delete_frames(db, token, frame_id):

@staticmethod
def update_frames(db, token, frame_id, frame_data):
user_id = decode_auth_token(token)
if user_id != None:
user_id = Utils.decode_auth_token(token)
if user_id is not None:
db.child('participants').child(user_id).child('frames').child(frame_id).remove()
upd_frame_id = db.child('participants').child(user_id).child('frames').push(frame_data)
frame = {"frame_id":upd_frame_id['name'], "frame_data":frame_data}
frame = {"frame_id": upd_frame_id['name'], "frame_data": frame_data}
return frame
else:
print('ERROR: Token Value is None')
return None
return None

@staticmethod
def check_email_address(db, email):
participants = db.child('participants').get().val()
if participants is None:
# No records found
return None
else:
for user_id in participants:
user = participants[user_id]
if user and (user["email"] == email):
return user_id
return None

@staticmethod
def change_password(db, user_id, password):
user_details = db.child('participants').child(user_id).get().val()
user_details['password'] = pbkdf2_sha256.hash(password)
db.child('participants').child(user_id).update(user_details)
73 changes: 63 additions & 10 deletions app/routes.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,71 @@
from flask import request, jsonify, make_response, abort
from app import app, Db
from flask import request, jsonify, make_response
from app import app, Db, mail
from utils import Utils
from flasgger.utils import swag_from

BASE_URL = '/api/v1'

database = Db.init_db()


@app.route(BASE_URL+'/')
@app.route(BASE_URL + '/')
def index():
return make_response(jsonify({"message": "DSC Frames API"})), 201


@app.route(BASE_URL+'/register', methods=['POST'])
@app.route(BASE_URL + '/send-reset-mail', methods=['POST'])
@swag_from('../docs/sendresetmail.yml')
def send_reset_mail():
data = request.json
if 'email' not in data.keys():
responseObject = {
'message': 'email not specified in the body'
}
return make_response(jsonify(responseObject)), 400
else:
emailAddr = data['email']
userId = Db.check_email_address(database, emailAddr)
if userId is not None:
resetLink = f'https://iwasat.events/reset.html?token={Utils.get_reset_token(userId)}'
Utils.send_reset_password_mail(mail, resetLink, emailAddr)
responseObject = {
'message': 'reset link was sent to the respective email address'
}
return make_response(jsonify(responseObject)), 200
else:
responseObject = {
'message': 'email doesnot match'
}
return make_response(jsonify(responseObject)), 401


@app.route(BASE_URL + '/update-password', methods=['POST'])
@swag_from('../docs/updatepassword.yml')
def update_password():
data = request.json
if 'token' not in data.keys() and 'password' not in data.keys():
responseObject = {
'message': 'token or password is absent in the request body'
}
return make_response(jsonify(responseObject)), 400
else:
token = data['token']
password = data['password']
userId = Utils.verify_reset_token(token)
if userId is None:
responseObject = {
'message': 'invalid reset token'
}
return make_response(jsonify(responseObject)), 401
else:
Db.change_password(database, userId, password)
responseObject = {
'message': 'password updated successfully'
}
return make_response(jsonify(responseObject)), 200


@app.route(BASE_URL + '/register', methods=['POST'])
@swag_from('../docs/register.yml')
def register():
user = request.json
Expand All @@ -24,19 +77,19 @@ def register():
return make_response(jsonify({"message": "Internal Server error"})), 500


@app.route(BASE_URL+'/login', methods=['POST'])
@app.route(BASE_URL + '/login', methods=['POST'])
@swag_from('../docs/login.yml')
def login():
user = request.json
user_data = Db.authorise_participants(database, user)
if (user_data != None and user_data[0] != None):
if (user_data is not None and user_data[0] is not None):
user_token = Db.get_token(database, user_data[0])
return make_response(jsonify({"token": user_token, "data": user_data[1]})), 202
else:
return make_response(jsonify({"message": "Login failed"})), 401


@app.route(BASE_URL+'/frames', methods=['POST', 'GET', 'DELETE', 'PUT'])
@app.route(BASE_URL + '/frames', methods=['POST', 'GET', 'DELETE', 'PUT'])
@swag_from('../docs/getframes.yml', methods=['GET'])
@swag_from('../docs/postframes.yml', methods=['POST'])
@swag_from('../docs/deleteframes.yml', methods=['DELETE'])
Expand All @@ -58,7 +111,7 @@ def frames():
frame = request.json['frame']
if frame:
responseObject = Db.save_frame(database, auth_token, frame)
if responseObject != None:
if responseObject is not None:
return make_response(jsonify(responseObject)), 201
else:
responseObject = {
Expand All @@ -72,7 +125,7 @@ def frames():
return make_response(jsonify(responseObject)), 400
elif request.method == 'GET':
frames_arr = Db.get_frames(database, auth_token)
if frames_arr != None:
if frames_arr is not None:
responseObject = {
'frames': frames_arr
}
Expand All @@ -98,7 +151,7 @@ def frames():
frame_id = request.json['frame_id']
frame_data = request.json['frame_data']
upd_frame = Db.update_frames(database, auth_token, frame_id, frame_data)
if upd_frame != None:
if upd_frame is not None:
responseObject = {
'message': 'Frame was updated successfully',
'data': upd_frame
Expand Down
Loading