Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ __pycache__/
.env
.envrc
.DS_Store
ca-certificate.crt
ca-certificate.crt
firebase-service-account-key.json
21 changes: 21 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@
from src.utils.team_loader import TeamLoader
from src.database import db

import os
import firebase_admin
from firebase_admin import credentials, auth

SERVICE_ACCOUNT_PATH = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")


def initialize_firebase():
if not firebase_admin._apps:
if not SERVICE_ACCOUNT_PATH:
raise ValueError(
"GOOGLE_APPLICATION_CREDENTIALS is not set. Set it to your firebase-service-account-key.json path."
)
cred = credentials.Certificate(SERVICE_ACCOUNT_PATH)
firebase_admin.initialize_app(cred)
logging.info("Firebase app initialized.")
return firebase_admin.get_app()


initialize_firebase()

app = Flask(__name__)

# CORS: allow frontend (different origin) to call this API
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ Flask-APScheduler
python-dotenv
pytz
gunicorn
firebase-admin
15 changes: 11 additions & 4 deletions src/mutations/login_user.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
from graphql import GraphQLError
from graphene import Mutation, String, Field
from graphene import Mutation, String

from firebase_admin import auth as firebase_auth
from flask_jwt_extended import create_access_token, create_refresh_token
from src.database import db


class LoginUser(Mutation):
class Arguments:
net_id = String(required=True, description="User's net ID (e.g. Cornell netid).")
id_token = String(required=True, description="Firebase ID token from the client.")

access_token = String()
refresh_token = String()

def mutate(self, info, net_id):
user = db["users"].find_one({"net_id": net_id})
def mutate(self, info, id_token):
try:
decoded = firebase_auth.verify_id_token(id_token)
except Exception:
raise GraphQLError("Invalid or expired token.")

firebase_uid = decoded["uid"]
user = db["users"].find_one({"firebase_uid": firebase_uid})
if not user:
raise GraphQLError("User not found.")
identity = str(user["_id"])
Expand Down
24 changes: 16 additions & 8 deletions src/mutations/signup_user.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
from graphql import GraphQLError
from graphene import Mutation, String

from firebase_admin import auth as firebase_auth
from flask_jwt_extended import create_access_token, create_refresh_token
from src.database import db


class SignupUser(Mutation):
class Arguments:
net_id = String(required=True, description="User's net ID (e.g. Cornell netid).")
id_token = String(required=True, description="Firebase ID token from the client.")
name = String(required=False, description="Display name.")
email = String(required=False, description="Email address.")
email = String(required=False, description="Email (overrides token email if provided).")

access_token = String()
refresh_token = String()

def mutate(self, info, net_id, name=None, email=None):
if db["users"].find_one({"net_id": net_id}):
raise GraphQLError("Net ID already exists.")
def mutate(self, info, id_token, name=None, email=None):
try:
decoded = firebase_auth.verify_id_token(id_token)
except Exception:
raise GraphQLError("Invalid or expired token.")

firebase_uid = decoded["uid"]
if db["users"].find_one({"firebase_uid": firebase_uid}):
raise GraphQLError("User already exists.")

email = email or decoded.get("email")
user_doc = {
"net_id": net_id,
"firebase_uid": firebase_uid,
"email": email,
"favorite_game_ids": [],
}
if name is not None:
user_doc["name"] = name
if email is not None:
user_doc["email"] = email
result = db["users"].insert_one(user_doc)
identity = str(result.inserted_id)
return SignupUser(
Expand Down
6 changes: 4 additions & 2 deletions src/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ class Mutation(ObjectType):
create_team = CreateTeam.Field(description="Creates a new team.")
create_youtube_video = CreateYoutubeVideo.Field(description="Creates a new youtube video.")
create_article = CreateArticle.Field(description="Creates a new article.")
login_user = LoginUser.Field(description="Login by net_id; returns access_token and refresh_token.")
login_user = LoginUser.Field(
description="Login with Firebase ID token; returns access_token and refresh_token.",
)
signup_user = SignupUser.Field(
description="Create a new user by net_id; returns access_token and refresh_token (no separate login needed).",
description="Create a new user with Firebase ID token; returns access_token and refresh_token.",
)
refresh_access_token = RefreshAccessToken.Field(
description="Exchange a valid refresh token (in Authorization header) for a new access_token.",
Expand Down