Skip to content

Commit 4ce488b

Browse files
Google oauth (#603)
* add google oauth option to sign up * add the kube host to docker compose file * removed the under score * fix the syntax in the google oauth logic * use config to access the environment variable for google oauth * add the status code * chore: add variables to staging.yml
1 parent 28d024f commit 4ce488b

File tree

7 files changed

+175
-4
lines changed

7 files changed

+175
-4
lines changed

.github/workflows/staging.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ jobs:
124124
--set environment.KUBE_SERVICE_PORT="${{ secrets.STAGING_KUBE_SERVICE_PORT }}" \
125125
--set environment.LOGGER_APP_URL="${{ secrets.MICROSERVICE_LOGGER_APP_URL }}" \
126126
--set environment.MLOPS_API_URL="${{ secrets.MICROSERVICE_MLOPS_API_URL }}" \
127+
--set environment.GOOGLE_CLIENT_ID="${{ secrets.STAGING_GOOGLE_CLIENT_ID }}" \
128+
--set environment.GOOGLE_CLIENT_SECRET="${{ secrets.STAGING_GOOGLE_CLIENT_SECRET }}" \
129+
--set environment.GOOGLE_REDIRECT_URI="${{ secrets.STAGING_GOOGLE_REDIRECT_URI }}" \
127130
--timeout=300s
128131
129132
- name: Monitor Rollout

api_docs.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,34 @@ paths:
497497
401:
498498
description: "failed to authenticate user"
499499

500+
"/users/oauth/google":
501+
get:
502+
tags:
503+
- auth
504+
consumes:
505+
- application/json
506+
description: "Login with google oauth"
507+
parameters:
508+
- in: body
509+
name: google code
510+
schema:
511+
type: object
512+
required:
513+
- code
514+
properties:
515+
code:
516+
type: string
517+
produces:
518+
- application/json
519+
responses:
520+
200:
521+
description: "success"
522+
400:
523+
description: "bad request"
524+
401:
525+
description: "failed to authenticate user"
526+
527+
500528
"/users/inactive_users":
501529
get:
502530
tags:

app/controllers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .users import (
55
UsersView, UserLoginView, UserEmailVerificationView, UserFollowersView, UserFollowView,
66
EmailVerificationRequest, ForgotPasswordView, ResetPasswordView, UserDisableView, UserEnableView,
7-
UserDetailView, AdminLoginView, OAuthView, UserDataSummaryView, UserAdminUpdateView, InActiveUsersView, SendInactiveUserMailReminder)
7+
UserDetailView, AdminLoginView, OAuthView, UserDataSummaryView, UserAdminUpdateView, InActiveUsersView, SendInactiveUserMailReminder,GoogleOAuthView,)
88
from .deployments import DeploymentsView
99
from .clusters import (
1010
ClustersView, ClusterDetailView, ClusterNamespacesView,

app/controllers/users.py

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,5 +1464,137 @@ def post(self):
14641464
status='success',
14651465
message=f'Successfully sent {emails_sent} reminder emails',
14661466
total_users_processed=len(inactive_users),
1467-
errors=errors if errors else None
1468-
), 201
1467+
errors=errors if errors else None),201
1468+
1469+
1470+
class GoogleOAuthView(Resource):
1471+
def get(self):
1472+
token_schema = UserSchema(partial=("password"),)
1473+
1474+
code = request.args.get('code')
1475+
if not code:
1476+
return dict(
1477+
status='fail',
1478+
message='No code received in query parameters'
1479+
), 400
1480+
1481+
1482+
token_data = {
1483+
'client_id': current_app.config.get('GOOGLE_CLIENT_ID'),
1484+
'client_secret': current_app.config.get('GOOGLE_CLIENT_SECRET'),
1485+
'code': code,
1486+
'grant_type': 'authorization_code',
1487+
'redirect_uri': current_app.config.get('GOOGLE_REDIRECT_URI') ,
1488+
}
1489+
1490+
try:
1491+
token_response = requests.post(
1492+
url='https://oauth2.googleapis.com/token',
1493+
data=token_data,
1494+
headers={'Content-Type': 'application/x-www-form-urlencoded'},
1495+
timeout=10
1496+
)
1497+
1498+
except requests.RequestException as e:
1499+
return dict(status='fail', message="Failed to connect to Google OAuth"), 500
1500+
1501+
if token_response.status_code != 200:
1502+
try:
1503+
error_data = token_response.json()
1504+
error_msg = error_data.get('error_description', error_data.get('error', 'Unknown error'))
1505+
except:
1506+
error_msg = f"HTTP {token_response.status_code}: {token_response.text}"
1507+
1508+
return dict(
1509+
status='fail',
1510+
message=f"Token exchange failed: {error_msg}"
1511+
), 401
1512+
1513+
try:
1514+
token_json = token_response.json()
1515+
except ValueError:
1516+
return dict(status='fail', message="Invalid JSON response from Google"), 500
1517+
1518+
if token_json.get('error'):
1519+
return dict(
1520+
status='fail',
1521+
message=f"Token error: {token_json.get('error_description', token_json['error'])}"
1522+
), 401
1523+
1524+
access_token = token_json.get('access_token')
1525+
if not access_token:
1526+
return dict(status='fail', message="No access token received"), 401
1527+
1528+
try:
1529+
user_response = requests.get(
1530+
url='https://www.googleapis.com/oauth2/v2/userinfo',
1531+
headers={'Authorization': f'Bearer {access_token}'},
1532+
timeout=10
1533+
)
1534+
1535+
except requests.RequestException as e:
1536+
return dict(status='fail', message="Failed to fetch user info"), 500
1537+
1538+
if user_response.status_code != 200:
1539+
return dict(status='fail', message="Failed to fetch user info"), 401
1540+
1541+
try:
1542+
user_data = user_response.json()
1543+
except ValueError:
1544+
return dict(status='fail', message="Invalid user data from Google"), 500
1545+
1546+
email = user_data.get('email')
1547+
name = user_data.get('name')
1548+
verified_email = user_data.get('verified_email', False)
1549+
1550+
if not email:
1551+
return dict(status='fail', message="Email not provided by Google"), 400
1552+
1553+
try:
1554+
user = User.find_first(email=email)
1555+
1556+
if not user:
1557+
user = User(
1558+
email=email,
1559+
name=name,
1560+
password=''.join((secrets.choice(string.ascii_letters)
1561+
for i in range(24))),
1562+
)
1563+
user.verified = verified_email
1564+
1565+
saved_user = user.save()
1566+
1567+
if not saved_user:
1568+
return dict(status='fail', message='Failed to create user'), 500
1569+
1570+
user.name = name
1571+
user.verified = verified_email
1572+
updated_user = user.save()
1573+
1574+
if not updated_user:
1575+
return dict(status='fail', message='Failed to update user'), 500
1576+
1577+
user_dict, errors = token_schema.dump(user)
1578+
1579+
if errors:
1580+
return dict(status='fail', message='User serialization error'), 500
1581+
1582+
access_token = user.generate_token(user_dict)
1583+
1584+
if not access_token:
1585+
return dict(status='fail', message="Failed to generate access token"), 500
1586+
1587+
return dict(
1588+
status='success',
1589+
data=dict(
1590+
access_token=access_token,
1591+
email=user.email,
1592+
name=user.name,
1593+
verified=user.verified,
1594+
id=str(user.id),
1595+
)
1596+
), 200
1597+
1598+
except Exception as e:
1599+
return dict(status='fail', message='Database error'), 500
1600+

app/routes/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
BillingInvoiceView, BillingInvoiceNotificationView, SystemSummaryView, CreditDetailView, ProjectUsersView, ProjectUsersTransferView, AppReviseView,
1616
ProjectUsersHandleInviteView, ClusterProjectsView, ProjectDisableView, ProjectEnableView, AppRedeployView, AppDisableView, AppEnableView,
1717
TagsView, TagsDetailView, TagFollowingView, GenericSearchView, MLProjectAppsView, ProjectMigrationView,
18-
UserDisableView, UserEnableView, AppDockerWebhookListenerView, UserFollowersView, UserFollowView, ProjectFollowingView, ActivityFeedView, SendInactiveUserMailReminder,)
18+
UserDisableView, UserEnableView, AppDockerWebhookListenerView, UserFollowersView, UserFollowView, ProjectFollowingView, ActivityFeedView, SendInactiveUserMailReminder,GoogleOAuthView)
1919
from app.controllers.app import AppRevisionsView
2020
from app.controllers.billing_invoice import BillingInvoiceDetailView
2121
from app.controllers.receipts import BillingReceiptsDetailView, BillingReceiptsView
@@ -36,6 +36,7 @@
3636
api.add_resource(ResetPasswordView, '/users/reset_password/<string:token>')
3737
api.add_resource(UserDetailView, '/users/<string:user_id>')
3838
api.add_resource(OAuthView, '/users/oauth')
39+
api.add_resource(GoogleOAuthView, '/users/oauth/google')
3940
api.add_resource(UserDataSummaryView, '/users/graph')
4041
api.add_resource(UserAdminUpdateView, '/users/admin_update')
4142
api.add_resource(InActiveUsersView, '/users/inactive_users',

config/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ class Base:
2828
# Github auth
2929
GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
3030
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
31+
# Google auth
32+
GOOGLE_CLIENT_ID= os.environ.get('GOOGLE_CLIENT_ID'),
33+
GOOGLE_CLIENT_SECRET= os.environ.get('GOOGLE_CLIENT_SECRET'),
34+
GOOGLE_REDIRECT_URI =os.environ.get('GOOGLE_REDIRECT_URI') ,
3135

3236
# celery
3337
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379")

docker-compose.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ services:
4444
- cranecloud
4545
- default
4646
environment:
47+
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
48+
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
49+
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI}
4750
KUBE_HOST:
4851
KUBE_TOKEN:
4952
KUBE_CERT:

0 commit comments

Comments
 (0)