diff --git a/.gitignore b/.gitignore
index 6ff0c709..424aaf9d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,7 +15,7 @@ __pycache__/
# Sphinx documentation
.doctrees/
-docs/*
+docs/**/
!docs/src
# Datasets
diff --git a/README.md b/README.md
index 1fef46c1..c8adc6f3 100644
--- a/README.md
+++ b/README.md
@@ -20,11 +20,11 @@ Prerequisites: MacOS or Linux environment
bash scripts/start.sh
```
-To destroy Rafiki's complete stack:
+To completely destroy Rafiki's stack:
- ```sh
- bash scripts/stop.sh
- ```
+```sh
+bash scripts/stop.sh
+```
More instructions are available in [Rafiki's Developer Guide](https://nginyc.github.io/rafiki/docs/latest/docs/src/dev).
diff --git a/conf.py b/docs/conf.py
similarity index 100%
rename from conf.py
rename to docs/conf.py
diff --git a/index.rst b/docs/index.rst
similarity index 95%
rename from index.rst
rename to docs/index.rst
index f1bdb11f..1c30f81b 100644
--- a/index.rst
+++ b/docs/index.rst
@@ -12,9 +12,9 @@ Index
.. toctree::
:maxdepth: 2
- docs/src/user/index.rst
- docs/src/dev/index.rst
- docs/src/python/index.rst
+ src/user/index.rst
+ src/dev/index.rst
+ src/python/index.rst
What is Rafiki?
--------------------------------------------------------------------
@@ -33,7 +33,6 @@ For *Model Developers*, they can:
- Contribute to Rafiki's pool of model templates
-
Check out :ref:`quick-setup` to deploy/develop Rafiki on your machine, and/or :ref:`quick-start` to use a deployed instance of Rafiki.
Issues
diff --git a/docs/src/dev/development.rst b/docs/src/dev/development.rst
index 8c94480f..2a9bc5ef 100644
--- a/docs/src/dev/development.rst
+++ b/docs/src/dev/development.rst
@@ -13,6 +13,18 @@ Before running any individual scripts, make sure to run the shell configuration
Refer to :ref:`architecture` and :ref:`folder-structure` for a developer's overview of Rafiki.
+Building Images Locally
+--------------------------------------------------------------------
+
+The quickstart instructions pull pre-built `Rafiki's images `_ from Docker Hub. To build Rafiki's images locally (e.g. to reflect latest code changes):
+
+ .. code-block:: shell
+
+ bash scripts/build_images.sh
+
+.. note::
+
+ If you're testing latest code changes on multiple nodes, you'll need to build Rafiki's images on those nodes as well.
Starting Parts of the Stack
--------------------------------------------------------------------
@@ -49,19 +61,6 @@ You can connect to Redis DB with `rebrow `_:
RAFIKI_ADDR=127.0.0.1
REDIS_EXT_PORT=6380
-Building Images Locally
---------------------------------------------------------------------
-
-The quickstart instructions pull pre-built `Rafiki's images `_ from Docker Hub. To build Rafiki's images locally (e.g. to reflect latest code changes):
-
- .. code-block:: shell
-
- bash scripts/build_images.sh
-
-.. note::
-
- If you're testing latest code changes on multiple nodes, you'll need to build Rafiki's images on those nodes as well.
-
Pushing Images to Docker Hub
--------------------------------------------------------------------
diff --git a/docs/src/dev/setup.rst b/docs/src/dev/setup.rst
index fc8ade4c..cf7f4c80 100644
--- a/docs/src/dev/setup.rst
+++ b/docs/src/dev/setup.rst
@@ -16,6 +16,10 @@ We assume development or deployment in a MacOS or Linux environment.
1. Install Docker 18 (`Ubuntu `__, `MacOS `__)
and, if required, add your user to ``docker`` group (`Linux `__).
+.. note::
+
+ If you're not a user in the ``docker`` group, you'll instead need ``sudo`` access and prefix every bash command with ``sudo -E``.
+
2. Install Python 3.6 (`Ubuntu `__, `MacOS `__)
3. Clone the project at https://github.com/nginyc/rafiki (e.g. with `Git `__)
diff --git a/docs/src/user/quickstart-admins.rst b/docs/src/user/quickstart-admins.rst
index d0cb8618..201ea4b7 100644
--- a/docs/src/user/quickstart-admins.rst
+++ b/docs/src/user/quickstart-admins.rst
@@ -40,16 +40,64 @@ Examples:
.. code-block:: python
client.create_user(
- email='app_developer@rafiki',
+ email='admin@rafiki',
password='rafiki',
- user_type='APP_DEVELOPER'
+ user_type='ADMIN'
)
-
+
client.create_user(
email='model_developer@rafiki',
password='rafiki',
user_type='MODEL_DEVELOPER'
)
+ client.create_user(
+ email='app_developer@rafiki',
+ password='rafiki',
+ user_type='APP_DEVELOPER'
+ )
+
+
+.. seealso:: :meth:`rafiki.client.Client.create_user`
+
+
+Listing all users
+--------------------------------------------------------------------
+
+Example:
+
+ .. code-block:: python
+
+ client.get_users()
+
+
+ .. code-block:: python
+
+ [{'email': 'superadmin@rafiki',
+ 'id': 'c815fa08-ce06-467d-941b-afc27684d092',
+ 'user_type': 'SUPERADMIN'},
+ {'email': 'admin@rafiki',
+ 'id': 'cb2c0d61-acd3-4b65-a5a7-d78aa5648283',
+ 'user_type': 'ADMIN'},
+ {'email': 'model_developer@rafiki',
+ 'id': 'bfe58183-9c69-4fbd-a7b3-3fdc267b3290',
+ 'user_type': 'MODEL_DEVELOPER'},
+ {'email': 'app_developer@rafiki',
+ 'id': '958a7d65-aa1d-437f-858e-8837bb3ecf32',
+ 'user_type': 'APP_DEVELOPER'}]
+
+
+.. seealso:: :meth:`rafiki.client.Client.get_users`
+
+
+Banning a user
+--------------------------------------------------------------------
+
+Example:
+
+ .. code-block:: python
+
+ client.ban_user('app_developer@rafiki')
+
-.. seealso:: :meth:`rafiki.client.Client.create_user`
\ No newline at end of file
+.. seealso:: :meth:`rafiki.client.Client.ban_user`
\ No newline at end of file
diff --git a/examples/scripts/seed_users.py b/examples/scripts/seed_users.py
index 177409cc..ab79efbe 100644
--- a/examples/scripts/seed_users.py
+++ b/examples/scripts/seed_users.py
@@ -1,12 +1,23 @@
import pprint
import os
+import csv
from rafiki.client import Client
from rafiki.config import SUPERADMIN_EMAIL, SUPERADMIN_PASSWORD
-def seed_users(client):
- users = client.create_users('examples/seeds/users.csv')
- pprint.pprint(users)
+def seed_users(client, csv_file_path):
+ with open(csv_file_path, 'rt', encoding='utf-8-sig') as f:
+ reader = csv.DictReader(f)
+ reader.fieldnames = [name.lower() for name in reader.fieldnames]
+ for row in reader:
+ email = row['email']
+ password = row['password']
+ user_type = row['user_type']
+ try:
+ client.create_user(email, password, user_type)
+ except Exception as e:
+ print('Failed to create user `{}` due to:'.format(email))
+ print(e)
if __name__ == '__main__':
rafiki_host = os.environ.get('RAFIKI_HOST', 'localhost')
@@ -14,9 +25,10 @@ def seed_users(client):
admin_web_port = int(os.environ.get('ADMIN_WEB_EXT_PORT', 3001))
user_email = os.environ.get('USER_EMAIL', SUPERADMIN_EMAIL)
user_password = os.environ.get('USER_PASSWORD', SUPERADMIN_PASSWORD)
+ csv_file_path = os.environ.get('CSV_FILE_PATH', 'examples/scripts/users.csv')
# Initialize client
client = Client(admin_host=rafiki_host, admin_port=admin_port)
client.login(email=user_email, password=user_password)
- seed_users(client)
\ No newline at end of file
+ seed_users(client, csv_file_path)
\ No newline at end of file
diff --git a/examples/scripts/users.csv b/examples/scripts/users.csv
new file mode 100644
index 00000000..05c9d8ad
--- /dev/null
+++ b/examples/scripts/users.csv
@@ -0,0 +1,4 @@
+EMAIL,PASSWORD,USER_TYPE
+admin@rafiki,rafiki,ADMIN
+model_developer@rafiki,rafiki,MODEL_DEVELOPER
+app_developer@rafiki,rafiki,APP_DEVELOPER
\ No newline at end of file
diff --git a/examples/seeds/users.csv b/examples/seeds/users.csv
deleted file mode 100644
index ae5c8626..00000000
--- a/examples/seeds/users.csv
+++ /dev/null
@@ -1,7 +0,0 @@
-EMAIL,PASSWORD,USER_TYPE
-john@rafiki,abcde,MODEL_DEVELOPER
-sarah@rafiki,fghij,MODEL_DEVELOPER
-aaron@rafiki,klmnop,MODEL_DEVELOPER
-diance@rafiki,qrstuv,MODEL_DEVELOPER
-peter@rafiki,wxyz,MODEL_DEVELOPER
-watson@rafiki,12345,MODEL_DEVELOPER
\ No newline at end of file
diff --git a/rafiki/admin/admin.py b/rafiki/admin/admin.py
index f1e98d28..21f32fbb 100644
--- a/rafiki/admin/admin.py
+++ b/rafiki/admin/admin.py
@@ -3,7 +3,6 @@
import traceback
import bcrypt
import uuid
-import csv
from rafiki.db import Database
from rafiki.constants import ServiceStatus, UserType, ServiceType, InferenceJobStatus, \
@@ -17,6 +16,7 @@
logger = logging.getLogger(__name__)
class UserExistsError(Exception): pass
+class UserAlreadyBannedError(Exception): pass
class InvalidUserError(Exception): pass
class InvalidPasswordError(Exception): pass
class InvalidRunningInferenceJobError(Exception): pass
@@ -42,7 +42,7 @@ def __init__(self, db=None, container_manager=None):
def seed(self):
with self._db:
- self._seed_users()
+ self._seed_superadmin()
####################################
# Users
@@ -59,39 +59,59 @@ def authenticate_user(self, email, password):
return {
'id': user.id,
- 'user_type': user.user_type
+ 'email': user.email,
+ 'user_type': user.user_type,
+ 'banned_date': user.banned_date
}
def create_user(self, email, password, user_type):
user = self._create_user(email, password, user_type)
return {
- 'id': user.id
+ 'id': user.id,
+ 'email': user.email,
+ 'user_type': user.user_type
}
- def create_users(self, csv_file_bytes):
- temp_csv_file = '{}.csv'.format(str(uuid.uuid4()))
-
- # Temporarily save the csv file to disk
- with open(temp_csv_file, 'wb') as f:
- f.write(csv_file_bytes)
-
- users = []
- with open(temp_csv_file, 'rt', encoding='utf-8-sig') as f:
- reader = csv.DictReader(f)
- reader.fieldnames = [name.lower() for name in reader.fieldnames]
- for row in reader:
- user = self._create_user(row['email'], row['password'], row['user_type'])
- users.append(user)
- os.remove(temp_csv_file)
+ def get_users(self):
+ users = self._db.get_users()
return [
{
'id': user.id,
'email': user.email,
- 'user_type': user.user_type
+ 'user_type': user.user_type,
+ 'banned_date': user.banned_date
}
for user in users
]
+ def get_user_by_email(self, email):
+ user = self._db.get_user_by_email(email)
+ if user is None:
+ return None
+
+ return {
+ 'id': user.id,
+ 'email': user.email,
+ 'user_type': user.user_type,
+ 'banned_date': user.banned_date
+ }
+
+ def ban_user(self, email):
+ user = self._db.get_user_by_email(email)
+ if user is None:
+ raise InvalidUserError()
+ if user.banned_date is not None:
+ raise UserAlreadyBannedError()
+
+ self._db.ban_user(user)
+
+ return {
+ 'id': user.id,
+ 'email': user.email,
+ 'user_type': user.user_type,
+ 'banned_date': user.banned_date
+ }
+
####################################
# Train Job
####################################
@@ -580,8 +600,8 @@ def get_models_of_task(self, user_id, task):
# Private / Users
####################################
- def _seed_users(self):
- logger.info('Seeding users...')
+ def _seed_superadmin(self):
+ logger.info('Seeding superadmin...')
# Seed superadmin
try:
diff --git a/rafiki/admin/app.py b/rafiki/admin/app.py
index c9019224..155473e3 100644
--- a/rafiki/admin/app.py
+++ b/rafiki/admin/app.py
@@ -3,9 +3,10 @@
import os
import traceback
import json
+from datetime import datetime
from rafiki.constants import UserType
-from rafiki.utils.auth import generate_token, decode_token, auth
+from rafiki.utils.auth import generate_token, decode_token, auth, UnauthorizedError
from .admin import Admin
@@ -20,27 +21,49 @@ def index():
# Users
####################################
-@app.route('/user', methods=['POST'])
+@app.route('/users', methods=['POST'])
@auth([UserType.ADMIN])
def create_user(auth):
admin = get_admin()
params = get_request_params()
+ # Only superadmins can create admins
+ if auth['user_type'] != UserType.SUPERADMIN and \
+ params['user_type'] in [UserType.ADMIN, UserType.SUPERADMIN]:
+ raise UnauthorizedError()
+
with admin:
return jsonify(admin.create_user(**params))
-@app.route('/users', methods=['POST'])
+@app.route('/users', methods=['GET'])
@auth([UserType.ADMIN])
-def create_users(auth):
+def get_users(auth):
admin = get_admin()
params = get_request_params()
- # Expect csv file as bytes
- csv_file_bytes = request.files['csv_file_bytes'].read()
- params['csv_file_bytes'] = csv_file_bytes
+ with admin:
+ return jsonify(admin.get_users(**params))
+
+@app.route('/users', methods=['DELETE'])
+@auth([UserType.ADMIN])
+def ban_user(auth):
+ admin = get_admin()
+ params = get_request_params()
with admin:
- return jsonify(admin.create_users(**params))
+ user = admin.get_user_by_email(params['email'])
+
+ if user is not None:
+ # Only superadmins can ban admins
+ if auth['user_type'] != UserType.SUPERADMIN and \
+ user['user_type'] in [UserType.ADMIN, UserType.SUPERADMIN]:
+ raise UnauthorizedError()
+
+ # Cannot ban yourself
+ if auth['user_id'] == user['id']:
+ raise UnauthorizedError()
+
+ return jsonify(admin.ban_user(**params))
@app.route('/tokens', methods=['POST'])
def generate_user_token():
@@ -51,12 +74,11 @@ def generate_user_token():
with admin:
user = admin.authenticate_user(**params)
- auth = {
- 'user_id': user['id'],
- 'user_type': user['user_type']
- }
+ # User cannot be banned
+ if user.get('banned_date') is not None and datetime.now() > user.get('banned_date'):
+ raise UnauthorizedError('User is banned')
- token = generate_token(auth)
+ token = generate_token(user)
return jsonify({
'user_id': user['id'],
diff --git a/rafiki/advisor/app.py b/rafiki/advisor/app.py
index 631e3401..a513f8a5 100644
--- a/rafiki/advisor/app.py
+++ b/rafiki/advisor/app.py
@@ -18,26 +18,6 @@
def index():
return 'Rafiki Advisor is up.'
-@app.route('/tokens', methods=['POST'])
-def generate_user_token():
- params = get_request_params()
-
- # Only superadmin can authenticate (other users must use Rafiki Admin)
- if not (params['email'] == SUPERADMIN_EMAIL and \
- params['password'] == SUPERADMIN_PASSWORD):
- raise UnauthorizedError()
-
- auth = {
- 'user_type': UserType.SUPERADMIN
- }
-
- token = generate_token(auth)
-
- return jsonify({
- 'user_type': auth['user_type'],
- 'token': token
- })
-
@app.route('/advisors', methods=['POST'])
@auth([UserType.ADMIN, UserType.APP_DEVELOPER])
def create_advisor(auth):
diff --git a/rafiki/client/client.py b/rafiki/client/client.py
index 155d2c43..58687a3c 100644
--- a/rafiki/client/client.py
+++ b/rafiki/client/client.py
@@ -36,6 +36,8 @@ def login(self, email, password):
App developers can create, list and stop train and inference jobs, as well as list models.
Model developers can create and list models.
+ The login session (the session token) expires in 1 hour.
+
:param str email: User's email
:param str password: User's password
@@ -80,7 +82,8 @@ def create_user(self, email, password, user_type):
'''
Creates a Rafiki user.
- Only admins can manage users.
+ Only admins can create users (except for admins).
+ Only superadmins can create admins.
:param str email: The new user's email
:param str password: The new user's password
@@ -90,33 +93,41 @@ def create_user(self, email, password, user_type):
:returns: Created user as dictionary
:rtype: dict[str, any]
'''
- data = self._post('/user', json={
+ data = self._post('/users', json={
'email': email,
'password': password,
'user_type': user_type
})
return data
- def create_users(self, csv_file_path):
+ def get_users(self):
'''
- Creates multiple Rafiki users.
-
- :param str csv_file_path: Path to a single csv file containing users to seed
+ Lists all Rafiki users.
+
+ Only admins can list all users.
- :returns: Created users as list of dictionaries
+ :returns: List of users
:rtype: dict[str, any][]
'''
+ data = self._get('/users')
+ return data
- f = open(csv_file_path, 'rb')
- csv_file_bytes = f.read()
- f.close()
+ def ban_user(self, email):
+ '''
+ Bans a Rafiki user, disallowing logins.
+
+ This action is irrevisible.
+ Only admins can ban users (except for admins).
+ Only superadmins can ban admins.
- data = self._post(
- '/users',
- files={
- 'csv_file_bytes': csv_file_bytes
- }
- )
+ :param str email: The user's email
+
+ :returns: Banned user as dictionary
+ :rtype: dict[str, any]
+ '''
+ data = self._delete('/users', json={
+ 'email': email
+ })
return data
####################################
diff --git a/rafiki/constants.py b/rafiki/constants.py
index 7e6665b5..8ff3a0de 100644
--- a/rafiki/constants.py
+++ b/rafiki/constants.py
@@ -48,7 +48,6 @@ class UserType():
ADMIN = 'ADMIN'
MODEL_DEVELOPER = 'MODEL_DEVELOPER'
APP_DEVELOPER = 'APP_DEVELOPER'
- USER = 'USER'
class AdvisorType():
BTB_GP = 'BTB_GP'
diff --git a/rafiki/db/database.py b/rafiki/db/database.py
index d9378fb8..2d1a3b6a 100644
--- a/rafiki/db/database.py
+++ b/rafiki/db/database.py
@@ -1,15 +1,17 @@
-import datetime
+from datetime import datetime
import os
from sqlalchemy import create_engine, distinct
from sqlalchemy.orm import sessionmaker
-from rafiki.constants import TrainJobStatus, \
+from rafiki.constants import TrainJobStatus, UserType, \
TrialStatus, ServiceStatus, InferenceJobStatus, ModelAccessRight
from .schema import Base, TrainJob, SubTrainJob, TrainJobWorker, \
InferenceJob, Trial, Model, User, Service, InferenceJobWorker, \
TrialLog
+class InvalidUserTypeError(Exception): pass
+
class Database(object):
def __init__(self,
host=os.environ.get('POSTGRES_HOST', 'localhost'),
@@ -36,6 +38,7 @@ def __init__(self,
####################################
def create_user(self, email, password_hash, user_type):
+ self._validate_user_type(user_type)
user = User(
email=email,
password_hash=password_hash,
@@ -44,10 +47,22 @@ def create_user(self, email, password_hash, user_type):
self._session.add(user)
return user
+ def ban_user(self, user):
+ user.banned_date = datetime.utcnow()
+ self._session.add(user)
+
def get_user_by_email(self, email):
user = self._session.query(User).filter(User.email == email).first()
return user
+ def get_users(self):
+ users = self._session.query(User).all()
+ return users
+
+ def _validate_user_type(self, user_type):
+ if user_type not in [UserType.SUPERADMIN, UserType.ADMIN, UserType.APP_DEVELOPER, UserType.MODEL_DEVELOPER]:
+ raise InvalidUserTypeError()
+
####################################
# Train Jobs
####################################
@@ -135,7 +150,7 @@ def mark_sub_train_job_as_running(self, sub_train_job):
def mark_sub_train_job_as_stopped(self, sub_train_job):
sub_train_job.status = TrainJobStatus.STOPPED
- sub_train_job.datetime_stopped = datetime.datetime.utcnow()
+ sub_train_job.datetime_stopped = datetime.utcnow()
self._session.add(sub_train_job)
return sub_train_job
@@ -206,13 +221,13 @@ def mark_inference_job_as_running(self, inference_job):
def mark_inference_job_as_stopped(self, inference_job):
inference_job.status = InferenceJobStatus.STOPPED
- inference_job.datetime_stopped = datetime.datetime.utcnow()
+ inference_job.datetime_stopped = datetime.utcnow()
self._session.add(inference_job)
return inference_job
def mark_inference_job_as_errored(self, inference_job):
inference_job.status = InferenceJobStatus.ERRORED
- inference_job.datetime_stopped = datetime.datetime.utcnow()
+ inference_job.datetime_stopped = datetime.utcnow()
self._session.add(inference_job)
return inference_job
@@ -287,12 +302,12 @@ def mark_service_as_running(self, service):
def mark_service_as_errored(self, service):
service.status = ServiceStatus.ERRORED
- service.datetime_stopped = datetime.datetime.utcnow()
+ service.datetime_stopped = datetime.utcnow()
self._session.add(service)
def mark_service_as_stopped(self, service):
service.status = ServiceStatus.STOPPED
- service.datetime_stopped = datetime.datetime.utcnow()
+ service.datetime_stopped = datetime.utcnow()
self._session.add(service)
def get_service(self, service_id):
@@ -418,14 +433,14 @@ def mark_trial_as_running(self, trial, knobs):
def mark_trial_as_errored(self, trial):
trial.status = TrialStatus.ERRORED
- trial.datetime_stopped = datetime.datetime.utcnow()
+ trial.datetime_stopped = datetime.utcnow()
self._session.add(trial)
return trial
def mark_trial_as_complete(self, trial, score, parameters):
trial.status = TrialStatus.COMPLETED
trial.score = score
- trial.datetime_stopped = datetime.datetime.utcnow()
+ trial.datetime_stopped = datetime.utcnow()
trial.parameters = parameters
self._session.add(trial)
return trial
@@ -437,7 +452,7 @@ def add_trial_log(self, trial, line, level):
def mark_trial_as_terminated(self, trial):
trial.status = TrialStatus.TERMINATED
- trial.datetime_stopped = datetime.datetime.utcnow()
+ trial.datetime_stopped = datetime.utcnow()
self._session.add(trial)
return trial
diff --git a/rafiki/db/schema.py b/rafiki/db/schema.py
index 337a80c1..d24b3067 100644
--- a/rafiki/db/schema.py
+++ b/rafiki/db/schema.py
@@ -2,7 +2,7 @@
from sqlalchemy import Column, String, Float, ForeignKey, Integer, Binary, DateTime
from sqlalchemy.dialects.postgresql import JSON, ARRAY
import uuid
-import datetime
+from datetime import datetime
from rafiki.constants import InferenceJobStatus, ServiceStatus, TrainJobStatus, \
TrialStatus, ModelAccessRight
@@ -13,7 +13,7 @@ def generate_uuid():
return str(uuid.uuid4())
def generate_datetime():
- return datetime.datetime.utcnow()
+ return datetime.utcnow()
class InferenceJob(Base):
__tablename__ = 'inference_job'
@@ -125,4 +125,5 @@ class User(Base):
email = Column(String, unique=True, nullable=False)
password_hash = Column(Binary, nullable=False)
user_type = Column(String, nullable=False)
+ banned_date = Column(DateTime, default=None)
\ No newline at end of file
diff --git a/rafiki/model/log.py b/rafiki/model/log.py
index 4b2e9045..1cbcfbca 100644
--- a/rafiki/model/log.py
+++ b/rafiki/model/log.py
@@ -1,6 +1,6 @@
import os
import traceback
-import datetime
+from datetime import datetime
import json
import logging
@@ -106,7 +106,7 @@ def set_logger(self, logger):
def _log(self, log_type, log_dict={}):
log_dict['type'] = log_type
- log_dict['time'] = datetime.datetime.now().strftime(MODEL_LOG_DATETIME_FORMAT)
+ log_dict['time'] = datetime.now().strftime(MODEL_LOG_DATETIME_FORMAT)
log_line = json.dumps(log_dict)
self._logger.info(log_line)
diff --git a/rafiki/utils/auth.py b/rafiki/utils/auth.py
index 7729728e..f49f739c 100644
--- a/rafiki/utils/auth.py
+++ b/rafiki/utils/auth.py
@@ -2,14 +2,22 @@
import os
import jwt
from functools import wraps
+from datetime import datetime, timedelta
from rafiki.constants import UserType
from rafiki.config import APP_SECRET
+TOKEN_EXPIRATION_HOURS = 1
+
class UnauthorizedError(Exception): pass
class InvalidAuthorizationHeaderError(Exception): pass
-def generate_token(payload):
+def generate_token(user):
+ payload = {
+ 'user_id': user['id'],
+ 'user_type': user['user_type'],
+ 'exp': datetime.utcnow() + timedelta(hours=TOKEN_EXPIRATION_HOURS)
+ }
token = jwt.encode(payload, APP_SECRET, algorithm='HS256')
return token.decode('utf-8')
@@ -18,7 +26,7 @@ def decode_token(token):
return payload
def auth(user_types=[]):
- # Superadmin can do anything
+ # Superadmins can do anything
user_types.append(UserType.SUPERADMIN)
def decorator(f):
diff --git a/scripts/build_docs.sh b/scripts/build_docs.sh
index 907c4bfe..5edd0047 100644
--- a/scripts/build_docs.sh
+++ b/scripts/build_docs.sh
@@ -6,4 +6,6 @@ else
fi
pip install sphinx sphinx_rtd_theme
-sphinx-build -b html . docs/$DOCS_DIR/
\ No newline at end of file
+sphinx-build -b html docs/ docs/$DOCS_DIR/
+
+echo "Generated documentation site at ./docs/$DOCS_DIR/index.html"
\ No newline at end of file
diff --git a/scripts/save_db.sh b/scripts/save_db.sh
index 2708b455..115fb929 100644
--- a/scripts/save_db.sh
+++ b/scripts/save_db.sh
@@ -7,8 +7,9 @@ then
if [ $ok = "n" ]
then
echo "Not dumping database!"
- else
- echo "Dumping database to $DUMP_FILE..."
- docker exec $POSTGRES_HOST pg_dump -U postgres --if-exists --clean $POSTGRES_DB > $DUMP_FILE
+ exit 0
fi
fi
+
+echo "Dumping database to $DUMP_FILE..."
+docker exec $POSTGRES_HOST pg_dump -U postgres --if-exists --clean $POSTGRES_DB > $DUMP_FILE
diff --git a/scripts/utils.sh b/scripts/utils.sh
index 201f3795..fcd4c729 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -20,7 +20,7 @@ ensure_stable()
echo "$1 is running"
else
echo "Error running $1"
- echo "Maybe $1 hasn't previously been stopped - try running `scripts/stop.sh`?"
+ echo "Maybe $1 hasn't previously been stopped - try running scripts/stop.sh?"
if ! [ -z "$LOG_FILE_PATH" ]
then
echo "Check the logs at $LOG_FILE_PATH"