Created
February 20, 2017 18:09
-
-
Save ghoshabhi/0aca7fd298b7f382036765d647a0b818 to your computer and use it in GitHub Desktop.
Flask App to authenticate user with Github API. The API is setup to use any front-end technology like React, Vue, etc. The code uses Web Application flow to authenticate users.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from flask import Flask, jsonify, redirect, \ | |
render_template, url_for | |
from flask import session as login_session | |
from flask_cors import CORS, cross_origin # My front end is with React and works on a different server | |
import requests | |
import random | |
import string | |
from credentials import client_id, client_secret # credentials.py has the client_id and client_secret | |
app = Flask(__name__) | |
CORS(app) #allows CORS on all routes | |
authorization_base_url = 'https://github.com/login/oauth/authorize' | |
token_url = 'https://github.com/login/oauth/access_token' | |
request_url = 'https://api.github.com' | |
# 1. Shows login page with a random 'state' parameter to prevent CSRF | |
@app.route('/') | |
def showLogin(): | |
''' | |
Shows login page. Sends a random 'state' parameter to the page | |
to prevent csrf. If you have your front-end running on a different | |
server you can choose to create the 'state' value their or send the | |
value from this endpoint. | |
The value of 'state' is also stored in session object for future use. | |
Returns a random string for 'state' and an optional template. | |
''' | |
state = ''.join(random.choice(string.ascii_uppercase + string.digits) | |
for x in xrange(32)) | |
login_session['state'] = state | |
# return jsonify(state=state) # to return state in a json response | |
return render_template('login.html', state=state) | |
# 1. Send initial request to get permissions from the user | |
@app.route('/handleLogin', methods=["GET"]) | |
def handleLogin(): | |
''' | |
This method makes initial request to get authorization | |
permissions from the user. It has the following parameters: | |
1. client_id: You can create one at - https://github.com/settings/applications/new | |
2. state: Random string used to prevent CSRF | |
3. scope: scope of permissions | |
You will setup a Authorization Callback URL when you create your new application on | |
Github. If the request is successfull Github redirects to that URL with a 'code' | |
parameter. | |
Requests temporary 'code' value from Github. | |
''' | |
if login_session['state'] == request.args.get('state'): | |
#print login_session['state'] | |
fetch_url = authorization_base_url + \ | |
'?client_id=' + client_id + \ | |
'&state=' + login_session['state'] + \ | |
'&scope=user%20repo%20public_repo' + \ | |
'&allow_signup=true' | |
#print fetch_url | |
return redirect(fetch_url) | |
else: | |
return jsonify(invalid_state_token="invalid_state_token") | |
#2. Using the /callback route to handle authentication. | |
@app.route('/callback', methods=['GET', 'POST']) | |
def handle_callback(): | |
''' | |
This function helps exchange temporary 'code' value with a permanent | |
access_token. | |
''' | |
if request.args.get('state') != login_session['state']: | |
response = make_response(json.dumps('Invalid state parameter!'), 401) | |
response.headers['Content-Type'] = 'application/json' | |
return response | |
if 'code' in request.args: | |
#return jsonify(code=request.args.get('code')) | |
payload = { | |
'client_id': client_id, | |
'client_secret': client_secret, | |
'code': request.args['code'] | |
} | |
headers = {'Accept': 'application/json'} | |
req = requests.post(token_url, params=payload, headers=headers) | |
resp = req.json() | |
if 'access_token' in resp: | |
login_session['access_token'] = resp['access_token'] | |
return jsonify(access_token=resp['access_token']) | |
#return redirect(url_for('index')) | |
else: | |
return jsonify(error="Error retrieving access_token"), 404 | |
else: | |
return jsonify(error="404_no_code"), 404 | |
# 3. Get user information from Github | |
@app.route('/index') | |
def index(): | |
# Check for access_token in session | |
if 'access_token' not in login_session: | |
return 'Never trust strangers', 404 | |
# Get user information from github api | |
access_token_url = 'https://api.github.com/user?access_token={}' | |
r = requests.get(access_token_url.format(login_session['access_token'])) | |
try: | |
resp = r.json() | |
gh_profile = resp['html_url'] | |
username = resp['login'] | |
avatar_url = resp['avatar_url'] | |
bio = resp['bio'] | |
name = resp['name'] | |
return jsonify( | |
gh_profile=gh_profile, | |
gh_username=username, | |
avatar_url=avatar_url, | |
gh_bio=bio, | |
name=name | |
) | |
except AttributeError: | |
app.logger.debug('error getting username from github, whoops') | |
return "I don't know who you are; I should, but regretfully I don't", 500 | |
# This endpoint fetches list of repositories of a user | |
@app.route('/user/<string:username>') | |
def getRepos(username): | |
if not 'access_token' in login_session: | |
invalid_access_token="Access token has expired or not in session" | |
app.logger.error(invalid_access_token) | |
return jsonify(invalid_access_token=invalid_access_token) | |
if not username: | |
return jsonify(username_not_give="Github username needed to fetch \ | |
repos") | |
# ?per_page=N : Have N>500. By default Github only returns first 30 objects | |
# returned by any query and then the rest are obtained in a different ways. | |
# Like using the 'since' parameter to mention the last ID you saw or using | |
# the Link header. Read More about it at: https://developer.github.com/v3/#pagination | |
url = request_url + '/users/{username}/repos?per_page=500'.format(username=username) | |
headers = {'Accept': 'application/json'} | |
try: | |
req = requests.get(url, headers=headers, timeout=4) | |
except (requests.exceptions.Timeout), e: | |
return jsonify("connection timed out") | |
if req.status_code == 200: | |
resp = req.json() | |
try: | |
app.logger.debug("Try to get repository names from response") | |
repo_info = [] | |
for each_repo in resp: | |
repo_dict = {} | |
repo_dict['repo_name'] = each_repo['full_name'] | |
repo_dict['repo_link'] = each_repo['html_url'] | |
repo_dict['description'] = each_repo['description'] | |
repo_dict['owner_fullname'] = each_repo['owner']['login'] | |
repo_dict['html_url'] = each_repo['html_url'] | |
repo_info.append(repo_dict) | |
app.logger.debug("Successfully fetched repository info from response") | |
return jsonify( | |
repo_count=len(repo_info), | |
repo_info=repo_info | |
), 200 | |
except (TypeError, AttributeError, KeyError), e: | |
app.logger.error(e) | |
return jsonify(no_user_found="no user found"), 404 | |
else: | |
res = req.json()['message'] | |
return jsonify(error=res) | |
# Get commits on a repository by username | |
@app.route('/user/<string:username>/<string:repo_name>/commits') | |
def getCommits(username, repo_name): | |
if not 'access_token' in login_session: | |
invalid_access_token="Access token has expired or not in session" | |
app.logger.error(invalid_access_token) | |
return jsonify(invalid_access_token=invalid_access_token) | |
if not username and not repo_name: | |
return jsonify(username_not_give="Github username or repo_name missing") | |
url = request_url + '/repos/{username}/{repo_name}/commits'\ | |
.format(username=username, repo_name=repo_name) | |
headers = {'Accept': 'application/json'} | |
res = requests.get(url, headers=headers) | |
commits = res.json() | |
try: | |
app.logger.info("Try to get commits information inside getCommits") | |
commit_info = [] | |
for commit in commits: | |
commit_dict = {} | |
commit_dict['commit_author'] = commit['commit']['author']['name'] | |
commit_dict['commit_date'] = commit['commit']['author']['date'] | |
commit_dict['commit_msg'] = commit['commit']['message'] | |
commit_info.append(commit_dict) | |
app.logger.info("Commit info retrieval successfull") | |
return jsonify(commits=commit_info) | |
except (TypeError, AttributeError, KeyError), e: | |
app.logger.error(e) | |
return jsonify(error=e) | |
if __name__ == "__main__": | |
app.secret_key = "fart_fart" | |
app.debug = True | |
app.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment