Skip to content

Commit 045b63b

Browse files
committed
jank oAuth implement
1 parent 68707a8 commit 045b63b

File tree

8 files changed

+137
-11
lines changed

8 files changed

+137
-11
lines changed

backend/conduit/app.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
# -*- coding: utf-8 -*-
22
"""The app module, containing the app factory function."""
33
from flask import Flask
4-
from conduit.extensions import bcrypt, cache, db, migrate, jwt, cors
4+
from conduit.extensions import bcrypt, cache, db, migrate, jwt, cors, github
55
from conduit import commands, user, profile, articles, organizations, tags
66
from conduit.settings import ProdConfig
77
from conduit.exceptions import InvalidUsage
88

9-
109
def create_app(config_object=ProdConfig):
1110
"""An application factory, as explained here:
1211
http://flask.pocoo.org/docs/patterns/appfactories/.
@@ -23,14 +22,16 @@ def create_app(config_object=ProdConfig):
2322
register_commands(app)
2423
return app
2524

26-
2725
def register_extensions(app):
2826
"""Register Flask extensions."""
2927
bcrypt.init_app(app)
3028
cache.init_app(app)
3129
db.init_app(app)
3230
migrate.init_app(app, db)
3331
jwt.init_app(app)
32+
app.config['GITHUB_CLIENT_ID'] = '98574e099fa640413899'
33+
app.config['GITHUB_CLIENT_SECRET'] = '272ac3010797de4cc29c5c0caf0bbd9df4d79832'
34+
github.init_app(app)
3435

3536

3637
def register_blueprints(app):

backend/conduit/extensions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from flask_jwt_extended import JWTManager
88
from flask_migrate import Migrate
99
from flask_sqlalchemy import SQLAlchemy, Model
10+
from flask_github import GitHub
1011

1112

1213
class CRUDMixin(Model):
@@ -51,6 +52,7 @@ def delete(self, commit=True):
5152
migrate = Migrate()
5253
cache = Cache()
5354
cors = CORS()
55+
github = GitHub()
5456

5557
from conduit.utils import jwt_identity, identity_loader # noqa
5658

backend/conduit/settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ class Config(object):
3333
'https://bit-next-git-master.bitproject.now.sh',
3434
'https://staging.bitproject.org',
3535
'https://dev.bitproject.org',
36-
'https://bit-next.now.sh'
36+
'https://bit-next.now.sh',
37+
'https://github.com/login/oauth/authorize?client_id=98574e099fa640413899'
3738
]
3839
JWT_HEADER_TYPE = 'Token'
3940

backend/conduit/user/models.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@ class User(SurrogatePK, Model):
2424
linkedinLink = Column(db.String(100), nullable=True)
2525
website = Column(db.Text(), nullable = True)
2626
isAdmin = Column(db.Boolean, nullable=False, default=False)
27+
github_access_token = Column(db.String(255), nullable = True)
28+
github_id = Column(db.Integer, nullable = True)
2729
token: str = ''
2830

29-
def __init__(self, username, email, password=None, **kwargs):
31+
def __init__(self, username, email, password=None, github_access_token=None, **kwargs):
3032
"""Create instance."""
3133
db.Model.__init__(self, username=username, email=email, **kwargs)
3234
if password:
3335
self.set_password(password)
36+
if github_access_token:
37+
self.set_github_token(github_access_token)
3438
else:
3539
self.password = None
3640

@@ -42,6 +46,9 @@ def check_password(self, value):
4246
"""Check password."""
4347
return bcrypt.check_password_hash(self.password, value)
4448

49+
def set_github_token(self, github_access_token):
50+
self.github_access_token = github_access_token
51+
4552
def __repr__(self):
4653
"""Represent instance as a unique string."""
4754
return '<User({username!r})>'.format(username=self.username)

backend/conduit/user/views.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
# -*- coding: utf-8 -*-
22
"""User views."""
3-
from flask import Blueprint, request
3+
from flask import Blueprint, request, jsonify, g
44
from flask_apispec import use_kwargs, marshal_with
55
from flask_jwt_extended import jwt_required, jwt_optional, create_access_token, current_user
66
from sqlalchemy.exc import IntegrityError
77

88
from conduit.database import db
9+
from conduit.extensions import github
910
from conduit.exceptions import InvalidUsage
1011
from conduit.profile.models import UserProfile
1112
from .models import User
1213
from .serializers import user_schema
14+
import requests
1315

1416
blueprint = Blueprint('user', __name__)
1517

16-
1718
@blueprint.route('/api/users', methods=('POST',))
1819
@use_kwargs(user_schema)
1920
@marshal_with(user_schema)
@@ -26,7 +27,6 @@ def register_user(username, password, email, **kwargs):
2627
raise InvalidUsage.user_already_registered()
2728
return userprofile.user
2829

29-
3030
@blueprint.route('/api/users/login', methods=('POST',))
3131
@jwt_optional
3232
@use_kwargs(user_schema)
@@ -64,3 +64,63 @@ def update_user(**kwargs):
6464
kwargs['updated_at'] = user.created_at.replace(tzinfo=None)
6565
user.update(**kwargs)
6666
return user
67+
68+
#TODO:
69+
#1) we have to add the state to make sure no third party access when sending code
70+
#2) change this away from username, only allows me to call the thing username cause of user_schema.
71+
#if bit_token invalid and access_tok still valid, just reauthenticate with new code and stuff
72+
#if access_token invalid but bit_token valid, ignore until bit_token gets invalid
73+
74+
#Note: the parameter is username but it should be changed to github_code
75+
#i just get errors thrown if
76+
77+
@blueprint.route('/api/user/callback', methods = ('POST',))
78+
@use_kwargs(user_schema)
79+
@marshal_with(user_schema)
80+
def github_oauth(username, **kwargs):
81+
#refactor and hide these
82+
83+
#NOTE: use try catch block later
84+
payload = { 'client_id': "98574e099fa640413899",
85+
'client_secret': "272ac3010797de4cc29c5c0caf0bbd9df4d79832",
86+
'code': username,
87+
}
88+
header = {
89+
'Accept': 'application/json',
90+
}
91+
92+
auth_response = requests.post('https://github.com/login/oauth/access_token', params=payload, headers=header).json()
93+
94+
#if it's an error response, the access_token will not work (like if code is invalid)
95+
#it won't have access_token key-value pair
96+
#build in try catch!
97+
access_token = auth_response["access_token"]
98+
99+
auth_header = {"Authorization": "Bearer " + access_token}
100+
data_response = requests.get('https://api.github.com/user', headers=auth_header).json()
101+
email_response = requests.get('https://api.github.com/user/emails', headers=auth_header).json()
102+
103+
username = data_response["login"]
104+
email = email_response[0]["email"]
105+
github_id = data_response["id"]
106+
107+
user = User.query.filter_by(email=email).first()
108+
if user is None:
109+
userprofile = UserProfile(User(username, email, github_access_token = access_token).save()).save()
110+
user = userprofile.user
111+
112+
user.token = create_access_token(identity=user, fresh=True)
113+
return user
114+
115+
# Flask Migrate
116+
117+
# write code
118+
# run flaskdb migrate in the code
119+
# flaskdb upgrade in the code
120+
# Code isn't working because staging db uses staging code
121+
# Code isn't working on local because we don't have db
122+
123+
# When doing github auth, we need to use flask db migrate to be able to add our cols
124+
# to our remote db
125+
126+

components/profile/LoginForm.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { mutate } from "swr";
44

55
import ListErrors from "../common/ListErrors";
66
import UserAPI from "../../lib/api/user";
7+
import Authenticate from "./Authenticate";
78

89
const LoginForm = () => {
910
const [isLoading, setLoading] = React.useState(false);
@@ -20,6 +21,34 @@ const LoginForm = () => {
2021
[]
2122
);
2223

24+
let logging_in;
25+
if (typeof window !== "undefined"){
26+
const code = new URLSearchParams(window.location.search).get("code");
27+
if (code){
28+
logging_in = (<p>Redirecting to home page...</p>);
29+
React.useEffect(() => {
30+
31+
async function post_code(){
32+
try{
33+
const {data, status} = await UserAPI.post_code(code);
34+
console.log("begun await");
35+
if (data?.user){
36+
console.log(data.user)
37+
window.localStorage.setItem("user", JSON.stringify(data.user));
38+
mutate("user", data?.user);
39+
Router.push("/");
40+
}
41+
} catch(error){
42+
console.error(error);
43+
}
44+
}
45+
46+
post_code();
47+
}, [])
48+
}
49+
}
50+
51+
2352
const handleSubmit = async (e) => {
2453
e.preventDefault();
2554
setLoading(true);
@@ -77,8 +106,13 @@ const LoginForm = () => {
77106
</button>
78107
</fieldset>
79108
</form>
109+
<a href="https://github.com/login/oauth/authorize?client_id=98574e099fa640413899&scope=user+repo"
110+
className="btn btn-lg btn-primary pull-xs-left"
111+
>
112+
Sign in through GitHub REAL</a>
113+
{logging_in}
80114
</>
81115
);
82-
};
116+
}
83117

84118
export default LoginForm;

lib/api/user.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import axios from "axios";
22

33
import { SERVER_BASE_URL } from "../utils/constant";
44

5+
const url = require('url');
6+
57
const UserAPI = {
68
current: async () => {
79
const user: any = window.localStorage.getItem("user");
@@ -135,6 +137,25 @@ const UserAPI = {
135137
}
136138
},
137139
get: async (username) => axios.get(`${SERVER_BASE_URL}/profiles/${username}`),
140+
141+
post_code: async (username) => {
142+
try{
143+
const response = await axios.post(
144+
`${SERVER_BASE_URL}/user/callback`,
145+
JSON.stringify({ user: { username } }),
146+
{
147+
headers: {
148+
"Content-Type": "application/json",
149+
},
150+
}
151+
);
152+
return response;
153+
} catch (error){
154+
return error.response;
155+
}
156+
157+
},
158+
138159
};
139160

140161
export default UserAPI;

lib/utils/constant.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// export const SERVER_BASE_URL = `https://conduit.productionready.io/api`;
33

44
// Local backend url
5-
// export const SERVER_BASE_URL = `http://127.0.0.1:5000/api`;
5+
export const SERVER_BASE_URL = `http://127.0.0.1:5000/api`;
66

77
// Staging url
8-
export const SERVER_BASE_URL = `https://bit-devs-staging.herokuapp.com/api`;
8+
// export const SERVER_BASE_URL = `https://bit-devs-staging.herokuapp.com/api`;
99

1010
// Production url. ONLY USE IN PRODUCTION
1111
// export const SERVER_BASE_URL = `https://bit-devs.herokuapp.com/api`;

0 commit comments

Comments
 (0)