Skip to content
Merged

Dev #25

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a1d8595
#20 started working on google oauth.
seekheart Mar 4, 2018
3c85abb
#20 added validation of google tokens endpoint and tests. Updated hom…
seekheart Mar 4, 2018
0161dcb
#20 updated readme, dependencies, and docker-compose to support googl…
seekheart Mar 4, 2018
5fc7505
#20 added encrypted secrets for travis CI
seekheart Mar 4, 2018
8852f0c
Merge pull request #21 from seekheart/google-oauth
seekheart Mar 4, 2018
1d4ba83
#20 refactored jwt api to handle distributing access and refresh toke…
seekheart Mar 6, 2018
7bb0a0d
#20 refactored login resource and tests to make use of new jwt refact…
seekheart Mar 6, 2018
4151fb5
#20 updated google resource to refresh tokens for logged in google us…
seekheart Mar 6, 2018
4b76f64
#20 updated db init script to truely drop db.
seekheart Mar 6, 2018
44de7b9
#22 updated google resource to only need GET, added tests for google …
seekheart Mar 7, 2018
e02a4be
#22 fixed bug where token was always expired, had to move exp keys to…
seekheart Mar 7, 2018
64fdd21
#22 updated auth doc
seekheart Mar 7, 2018
a42a871
#22 fixed auth json
seekheart Mar 7, 2018
a72b2b0
Merge pull request #22 from seekheart/google-oauth
seekheart Mar 7, 2018
9ba02f7
added code climate
seekheart Mar 7, 2018
f84eb1e
Merge pull request #23 from seekheart/seekheart-patch-1
seekheart Mar 7, 2018
1a40694
applied travis changes to use coveralls
seekheart Mar 9, 2018
acce5fb
applied travis changes to use coverage reporter, added badge to readme
seekheart Mar 9, 2018
12eda65
refactored refresh token logic, by removing unused logic.
seekheart Mar 9, 2018
da16945
Merge pull request #24 from seekheart/coveralls
seekheart Mar 9, 2018
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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.json
.gitignore
.travis.yml
/google_secrets.json
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,5 @@ ENV/
.idea

#secrets
coder_directory_api/prod_settings.json
coder_directory_api/prod_settings.json
/google_secrets.json
18 changes: 16 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
env:
global:
- CC_TEST_REPORTER_ID=5fcffa3ecf8aa57eefeb35fceff9cd300db4af96a4287dc29e1b1a12ad46eb29
# These are the versions of our backend to be supported
language: python
python:
Expand All @@ -10,18 +13,29 @@ python:
install:
- "pip install -U setuptools"
- "pip install -r requirements.txt"
- "pip install coveralls"

# we use mongodb as our database
services:
- mongodb

# Seed the mongodb with some test data
before_install:
- openssl aes-256-cbc -K $encrypted_fedd13c2de32_key -iv $encrypted_fedd13c2de32_iv -in google_secrets.json.enc -out google_secrets.json -d

# Seed the mongodb with some test data, and setup test coverage reporters
before_script:
- bash mock_data/db_init.sh
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build

# Run the test suite
script:
- nosetests
- coverage run -m nose tests

after_success:
- coveralls
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT

# notify if there is a change in build status
notification:
Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
[![Build Status][travis]](https://travis-ci.org/seekheart/coder_directory_api)
[![License][license]](https://img.shields.io/badge/license-MIT%20License-blue.svg)
[![Version][version]](https://img.shields.io/badge/Version-1.0.0-brightgreen.svg)

[![Maintainability][maintain]](https://codeclimate.com/github/seekheart/coder_directory_api/maintainability)
[![Coverage Status][coverage]](https://coveralls.io/github/seekheart/coder_directory_api?branch=master)

The Coder Directory Api is a RESTful api developed to provide management of
coders and programming languages.
Expand All @@ -15,12 +16,14 @@ coders and programming languages.
| /register | Registers a user/app to use api |
| /login | Login user to obtain token |
| /login/token | Send your tokens here to refresh your access before it expires |
| /google | Sign in to google and get access token |
| /users | Access users resource for GET/POST |
| /users/{id} | Access users resource for GET/PATCH/DELETE for 1 user |
| /languages | Access language resource for GET/POST |
| /languages/{id} | Access language resource for GET/PATCH/DELETE of 1 language |

With the exception of the `register` and `login` endpoints all resources

With the exception of the `register`, `google`, and `login` endpoints all resources
require a jwt to be sent in the `Authorization` header with `Bearer` scheme.

## Development
Expand Down Expand Up @@ -77,6 +80,7 @@ The following variables need to be set.
* HOST - host address to run app on, defaults to localhost or 0.0.0.0
* PORT - port number to run on, defaults to 3000
* SECRET - path to your secret credentials json file.
* GOOGLE - path to your google credentials json file.

In addition for `APP_ENV` this variable will determine whether the app outputs
debug messages if not in `PROD` and whether or not `MULTITHREADING` for
Expand All @@ -89,12 +93,18 @@ signatures for JWT.
Included in the project is an example setup file: `dev_settings.json`


### GOOGLE credentials
In order to use google oauth you will need to register a service account with
[google].

## Author

* **Mike Tung** - *Main Developer* - [Github]

[Github]: https://github.com/seekheart
[travis]: https://travis-ci.org/seekheart/coder_directory_api.svg?branch=master
[license]: https://img.shields.io/badge/license-MIT%20License-blue.svg
[version]: https://img.shields.io/badge/Version-1.0.0-brightgreen.svg

[version]: https://img.shields.io/badge/Version-1.1.0-brightgreen.svg
[google]: https://console.developers.google.com
[maintain]: https://api.codeclimate.com/v1/badges/47c92b40567f27394cec/maintainability
[coverage]: https://coveralls.io/repos/github/seekheart/coder_directory_api/badge.svg?branch=master
7 changes: 5 additions & 2 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def create_app() -> Flask:
"""

api = Flask('__name__')
api.secret_key = SECRET_KEY
CORS(api)
api.wsgi_app = ProxyFix(api.wsgi_app)
api.url_map.strict_slashes = False
Expand Down Expand Up @@ -49,7 +50,9 @@ def register_resources(api, bp, route=None):

if __name__ == '__main__':
app = create_app()
app.run(host=HOST,
app.run(
host=HOST,
port=PORT,
debug=DEBUG,
threaded=MULTITHREADING)
threaded=MULTITHREADING,
)
1 change: 1 addition & 0 deletions coder_directory_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def create_app() -> Flask:
"""

api = Flask('__name__')
api.secret_key = SECRET_KEY
CORS(api)
api.wsgi_app = ProxyFix(api.wsgi_app)
api.url_map.strict_slashes = False
Expand Down
2 changes: 1 addition & 1 deletion coder_directory_api/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
MIT License, see LICENSE for details
"""

__all__ = ['check_token', 'token_required', 'make_token']
__all__ = ['check_token', 'token_required', 'make_token', 'refresh_token']
from .jwt_authorization import *
145 changes: 106 additions & 39 deletions coder_directory_api/auth/jwt_authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# set some global helpers
auth_engine = engines.AuthEngine()
secret = settings.SECRET_KEY
expire_time = datetime.timedelta(minutes=5)


def refresh_token(token) -> dict or None:
Expand All @@ -28,33 +29,42 @@ def refresh_token(token) -> dict or None:
Returns:
refreshed jwt token payload.
"""
user = token['user']
user_refresh_token = token['refresh_token']

try:
user = token['user']
token['access_token'] = jwt.decode(token['access_token'], secret)
token['refresh_token'] = jwt.decode(token['refresh_token'], secret)
except (jwt.ExpiredSignatureError, jwt.DecodeError, jwt.InvalidTokenError):
jwt.decode(token['refresh_token'], secret)
except (jwt.DecodeError, jwt.InvalidTokenError) as e:
return None

if user:
ref_token = auth_engine.find_one(user=user)
else:
return None

ref_token = auth_engine.find_one(user=user)
ref_token = ref_token['refresh_token']

if ref_token == token['refresh_token']:
token['access_token']['exp'] = datetime.datetime.utcnow() + \
datetime.timedelta(minutes=5)
result = auth_engine.edit_one(user=user, doc=token)
else:
try:
ref_token = ref_token.decode('utf-8')
except AttributeError:
return None

token['access_token'] = jwt.encode(
token['access_token'], secret
).decode('utf-8')
token['refresh_token'] = jwt.encode(
token['refresh_token'], secret
).decode('utf-8')
if ref_token == user_refresh_token:
new_access_token = make_access_token(user)
result = auth_engine.edit_one(
user=user,
doc={'access_token': new_access_token}
)
else:
return None

if result:
return token
user_doc = {
'user': user,
'access_token': new_access_token,
'refresh_token': token['refresh_token']
}
return make_payload(user_doc=user_doc)
else:
return None

Expand All @@ -78,7 +88,7 @@ def check_token(token) -> bool:
jwt.DecodeError,
jwt.InvalidTokenError,
KeyError
):
) as e:
result = False
else:
user = auth_engine.find_one(decoded_token['user'])
Expand Down Expand Up @@ -136,36 +146,21 @@ def make_token(user: str) -> dict:
for unauthenticated clients.
"""

renew_token = {
'iss': 'coder directory',
'sub': user,
'created': datetime.datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S'),
'jti': str(uuid.uuid4()),
'iat': make_timestamp(),
}

access_token = {
'iss': 'coder directory',
'user': user,
'jti': str(uuid.uuid4()),
'iat': make_timestamp(),
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
}
renew_token = make_refresh_token(user)
access_token = make_access_token(user)

tokens = {
'access_token': access_token,
'refresh_token': renew_token
}

auth_engine.edit_one(user=user, doc=tokens)

payload = {
user_doc = {
'user': user,
'created': datetime.datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S'),
'access_token': jwt.encode(access_token, secret).decode('utf-8'),
'refresh_token': jwt.encode(renew_token, secret).decode('utf-8')
'access_token': tokens['access_token'],
'refresh_token': tokens['refresh_token']
}

payload = make_payload(user_doc=user_doc)
return payload


Expand All @@ -178,3 +173,75 @@ def make_timestamp() -> int:
"""
date = int(datetime.datetime.utcnow().strftime('%s')) * 1000
return date


def make_payload(user_doc: dict) -> dict:
"""
Helper function to make payload for jwt tokens.
Args:
user_doc: dictionary containing user, access_token, refresh_token

Returns:
api payload for jwt token.
"""

try:
access_token = user_doc['access_token'].decode('utf-8')
except AttributeError:
access_token = user_doc['access_token']

try:
renew_token = user_doc['refresh_token'].decode('utf-8')
except AttributeError:
renew_token = user_doc['refresh_token']
return {
'user': user_doc['user'],
'created': datetime.datetime.now().strftime('%m/%d/%Y %H:%M:%S'),
'expires_in': expire_time.seconds,
'access_token': access_token,
'refresh_token': renew_token
}


def make_access_token(user_name: str) -> dict:
"""
Helper function to make the access token.

Args:
user_name: username to make token for.

Returns:
encrypted jwt access token
"""
return jwt.encode(
{
'iss': 'coder directory',
'user': user_name,
'jti': str(uuid.uuid4()),
'iat': make_timestamp(),
'exp': datetime.datetime.utcnow() + expire_time
},
secret
)


def make_refresh_token(user_name: str) -> dict:
"""
Helper function to make refresh token.

Args:
user_name: username to make token for.

Returns:
encrypted jwt refresh token.
"""

return jwt.encode(
{
'iss': 'coder directory',
'sub': user_name,
'created': datetime.datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S'),
'jti': str(uuid.uuid4()),
'iat': make_timestamp(),
},
secret)
4 changes: 3 additions & 1 deletion coder_directory_api/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
from .home import api as home_api
from .login import api as login_api
from .register import api as register_api
from .google import api as google_api

# Create a list of resource objects to register in api
api_resources = [
{'bp': languages_api, 'route': 'languages'},
{'bp': users_api, 'route': 'users'},
{'bp': home_api, 'route': None},
{'bp': login_api, 'route': 'login'},
{'bp': register_api, 'route': 'register'}
{'bp': register_api, 'route': 'register'},
{'bp': google_api, 'route': 'google'}
]
Loading