Skip to content

Commit bfeccfd

Browse files
committed
Initial implementation of userDB and associated APIs in server
1 parent 8d7fa14 commit bfeccfd

File tree

3 files changed

+189
-2
lines changed

3 files changed

+189
-2
lines changed

lib/pbench/server/api/__init__.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import requests
1212
import humanize
1313
import tempfile
14+
from flask_bcrypt import Bcrypt
15+
from flask_sqlalchemy import SQLAlchemy
1416

1517
from flask import request, jsonify, Flask, make_response
1618
from flask_restful import Resource, abort, Api
@@ -24,7 +26,8 @@
2426
ALLOWED_EXTENSIONS = {"xz"}
2527

2628
app = None
27-
29+
bcrypt = None
30+
db = None
2831

2932
def allowed_file(filename):
3033
"""Check if the file has the correct extension."""
@@ -51,7 +54,7 @@ def register_endpoints(api, app):
5154
def create_app():
5255
"""Create Flask app with defined resource endpoints."""
5356

54-
global app
57+
global app, db, bcrypt
5558

5659
cfg_name = os.environ.get("_PBENCH_SERVER_CONFIG")
5760
if not cfg_name:
@@ -103,6 +106,14 @@ def create_app():
103106
app.config["BIND_HOST"] = app.config_server.get("bind_host")
104107
app.config["WORKERS"] = app.config_server.get("workers")
105108

109+
# postgresql specific configuration
110+
app.config["DB_SECRET_KEY"] = os.getenv("SECRET_KEY", "my_precious")
111+
app.config["BCRYPT_LOG_ROUNDS"] = app.config_server.get("bycrypt_log_rounds")
112+
app.config["POSTGRES_DB_URI"] = app.config_server.get("postgres_db_uri")
113+
114+
bcrypt = Bcrypt(app)
115+
db = SQLAlchemy(app)
116+
106117
register_endpoints(api, app)
107118

108119
return app
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import jwt
2+
import datetime
3+
from dateutil import parser
4+
from pbench.server.api import app, bcrypt, db
5+
6+
7+
class UserModel(db.Model):
8+
""" User Model for storing user related details """
9+
__tablename__ = "users"
10+
11+
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
12+
username = db.Column(db.String(255), unique=True, nullable=False)
13+
firstName = db.Column(db.String(255), unique=False, nullable=False)
14+
lastName = db.Column(db.String(255), unique=False, nullable=False)
15+
password = db.Column(db.String(255), nullable=False)
16+
registered_on = db.Column(db.DateTime, nullable=False)
17+
# ToDo: decide if we want the email field
18+
19+
def __init__(self, username, firstName, lastName, password):
20+
self.username = username
21+
self.firstName = firstName
22+
self.lastName = lastName
23+
self.password = bcrypt.generate_password_hash(
24+
password, app.config.get('BCRYPT_LOG_ROUNDS')
25+
).decode()
26+
self.registered_on = datetime.datetime.now()
27+
28+
def __str__(self):
29+
return f"New user, id: {self.id}, username: {self.username}"
30+
31+
def encode_auth_token(self, user_id):
32+
"""
33+
Generates the Auth Token
34+
:return: string
35+
"""
36+
try:
37+
payload = {
38+
'iat': datetime.datetime.utcnow(),
39+
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5),
40+
'sub': user_id
41+
}
42+
return jwt.encode(
43+
payload,
44+
app.config.get('SECRET_KEY'),
45+
algorithm='HS256'
46+
)
47+
except Exception as e:
48+
return e
49+
50+
@staticmethod
51+
def decode_auth_token(auth_token):
52+
"""
53+
Validates the auth token
54+
:param auth_token:
55+
:return: integer|string
56+
"""
57+
try:
58+
payload = jwt.decode(auth_token, app.config.get('SECRET_KEY'))
59+
return payload['sub']
60+
except jwt.ExpiredSignatureError:
61+
return 'Signature expired. Please log in again.'
62+
except jwt.InvalidTokenError:
63+
return 'Invalid token. Please log in again.'
64+
65+
66+
class URLModel(db.Model):
67+
""" User Model for storing user related details """
68+
__tablename__ = "urls"
69+
70+
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
71+
created = db.Column(db.DateTime, nullable=False)
72+
updated = db.Column(db.DateTime, nullable=False)
73+
config = db.Column(db.String(255), unique=False, nullable=False)
74+
description = db.Column(db.String(255), nullable=False)
75+
76+
def __init__(self, created, config, description):
77+
self.created = parser.parse(created)
78+
self.updated = datetime.datetime.now()
79+
self.config = config
80+
self.description = description
81+
82+
def __str__(self):
83+
return f"New user, id: {self.id}, username: {self.username}"
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
2+
from flask import request, jsonify, make_response
3+
from flask_restful import Resource, abort
4+
from pbench.server.api import app, bcrypt, db
5+
from pbench.server.api.resources.db_models import UserModel
6+
7+
8+
class RegisterUser(Resource):
9+
"""
10+
Abstracted pbench API for registering a new user
11+
"""
12+
13+
def post(self):
14+
# get the post data
15+
post_data = request.get_json()
16+
# check if user already exists
17+
user = UserModel.query.filter_by(email=post_data.get('username')).first()
18+
if not user:
19+
try:
20+
user = UserModel(
21+
username=post_data.get('username'),
22+
password=post_data.get('password'),
23+
firstName=post_data.get('firstName'),
24+
lastName=post_data.get('lastName'),
25+
)
26+
27+
# insert the user
28+
db.session.add(user)
29+
db.session.commit()
30+
app.logger.info(f"New user registered, id: {self.id}, username: {self.username}")
31+
32+
# generate the auth token
33+
auth_token = user.encode_auth_token(user.id)
34+
response_object = {
35+
'status': 'success',
36+
'status_code': 201,
37+
'message': 'Successfully registered.',
38+
'auth_token': auth_token.decode()
39+
}
40+
return make_response(jsonify(response_object))
41+
42+
except Exception as e:
43+
response_object = {
44+
'status': 'fail',
45+
'status_code': 401,
46+
'message': 'Some error occurred. Please try again.'
47+
}
48+
return make_response(jsonify(response_object))
49+
else:
50+
response_object = {
51+
'status': 'fail',
52+
'status_code': 202,
53+
'message': 'User already exists. Please Log in.',
54+
}
55+
return make_response(jsonify(response_object))
56+
57+
58+
class LoginAPI(Resource):
59+
"""
60+
Abstracted pbench API for User Login
61+
"""
62+
def post(self):
63+
# get the post data
64+
post_data = request.get_json()
65+
try:
66+
# fetch the user data
67+
user = UserModel.query.filter_by(username=post_data.get('username')).first()
68+
69+
if user and bcrypt.check_password_hash(user.password, post_data.get('password')):
70+
auth_token = user.encode_auth_token(user.id)
71+
if auth_token:
72+
response_object = {
73+
'status': 'success',
74+
'status_code': 200,
75+
'message': 'Successfully logged in.',
76+
'auth_token': auth_token.decode()
77+
}
78+
return make_response(jsonify(response_object))
79+
else:
80+
response_object = {
81+
'status': 'fail',
82+
'status_code': 404,
83+
'message': 'User does not exist.'
84+
}
85+
return make_response(jsonify(response_object))
86+
except Exception as e:
87+
app.logger.info(f"Exception occurred during user login {e}")
88+
response_object = {
89+
'status': 'fail',
90+
'status_code': 500,
91+
'message': 'Try again'
92+
}
93+
return make_response(jsonify(response_object))

0 commit comments

Comments
 (0)