-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API supabase #593
Merged
Merged
API supabase #593
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
072d591
change firebase authentication to class
dejima-shikou e6d6d9f
fix auth code
dejima-shikou 75817ff
change auth api
dejima-shikou 8c60a66
Merge branch 'main' into topic/api-supabase
dejima-shikou 3b222f0
add supabase_auth_module
dejima-shikou 6d950a4
add supabase
dejima-shikou 380a0ed
fix SUPABASE_PUBLIC_URL
dejima-shikou ca26ad6
change url
dejima-shikou 0a0a931
set network
dejima-shikou 09f5d49
change how the supabase response is referenced
dejima-shikou e35ebc6
add AUTH_SERVICE in env
dejima-shikou 693a2ea
set TESTDB_HOST in env
dejima-shikou 375dd17
ignore supabase db data
dejima-shikou a2f7c2d
add check_token
dejima-shikou 190fcb7
change deprecated method
dejima-shikou 4444b5d
fix mypy warning
dejima-shikou 37f83ad
move account.py
dejima-shikou 5f1106b
fix black error
dejima-shikou 789173a
set env AUTH_SERVICE in Pipenv CI
dejima-shikou 239e3f5
delete unnecessary comment
dejima-shikou 223c0e9
delete wrong network
dejima-shikou 03fcdc4
raise HTTPException from routers
dejima-shikou 34af069
Merge branch 'main' into topic/api-supabase
dejima-shikou f94e0bb
move http_excption_creator to utils
dejima-shikou 15cc81e
put __init__
dejima-shikou File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,7 @@ env: | |
FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }} | ||
SENDGRID_API_KEY: fake_api_key_for_sendgrid_test | ||
SYSTEM_EMAIL: [email protected] | ||
AUTH_SERVICE: FIREBASE | ||
|
||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel | ||
jobs: | ||
|
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
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
Large diffs are not rendered by default.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Empty file.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from fastapi import Depends, HTTPException, status | ||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer | ||
from sqlalchemy.orm import Session | ||
|
||
from app import persistence | ||
from app.auth.auth_exception import AuthException | ||
from app.auth.auth_module import AuthModule, get_auth_module | ||
from app.routers.http_excption_creator import create_http_excption | ||
|
||
from ..database import get_db | ||
from ..models import Account | ||
|
||
|
||
def get_current_user( | ||
token: HTTPAuthorizationCredentials = Depends( | ||
HTTPBearer(scheme_name=None, description=None, auto_error=False) | ||
), | ||
auth_module: AuthModule = Depends(get_auth_module), | ||
db: Session = Depends(get_db), | ||
) -> Account: | ||
try: | ||
uid, email = auth_module.check_and_get_user_info(token) | ||
except AuthException as auth_exception: | ||
raise create_http_excption(auth_exception) | ||
user = persistence.get_account_by_uid(db, uid) | ||
if not user: | ||
raise HTTPException( | ||
status_code=status.HTTP_404_NOT_FOUND, | ||
detail="No such user", | ||
) | ||
if user.disabled: | ||
raise HTTPException( | ||
status_code=status.HTTP_400_BAD_REQUEST, | ||
detail="Inactive user", | ||
) | ||
return user |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import enum | ||
|
||
|
||
class AuthErrorType(str, enum.Enum): | ||
UNAUTHORIZED = "unauthorized" | ||
INTERNAL_SERVER_ERROR = "internal_server_error" | ||
SERVICE_UNAVAILABLE = "service_unavailable" | ||
|
||
|
||
class AuthException(Exception): | ||
def __init__(self, error_type: AuthErrorType, message: str): | ||
super().__init__() | ||
self.__error_type = error_type | ||
self.__message = message | ||
|
||
@property | ||
def error_type(self): | ||
return self.__error_type | ||
|
||
@property | ||
def message(self): | ||
return self.__message |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from fastapi import HTTPException, status | ||
|
||
from ..schemas import Token | ||
|
||
|
||
def get_auth_module(): | ||
return AuthModule(None) | ||
|
||
|
||
class AuthModule: | ||
def __init__(self): | ||
pass | ||
|
||
def login_for_access_token(self, username, password) -> Token: | ||
return Token(access_token="", token_type="bearer", refresh_token="") | ||
|
||
def refresh_access_token(self, refresh_token) -> Token: | ||
return Token(access_token="", token_type="bearer", refresh_token="") | ||
|
||
def check_and_get_user_info(self, token): | ||
pass | ||
|
||
def check_token(self, token): | ||
if token is None: | ||
raise HTTPException( | ||
status_code=status.HTTP_401_UNAUTHORIZED, | ||
detail="Could not validate credentials", | ||
headers={"WWW-Authenticate": "Bearer"}, | ||
) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import json | ||
import os | ||
|
||
import requests | ||
from firebase_admin import auth, credentials, initialize_app | ||
|
||
from app.auth.auth_exception import AuthErrorType, AuthException | ||
from app.auth.auth_module import AuthModule | ||
|
||
from ..schemas import Token | ||
|
||
|
||
class FirebaseAuthModule(AuthModule): | ||
def __init__(self): | ||
super().__init__() | ||
self.cred = credentials.Certificate(os.environ["FIREBASE_CRED"]) | ||
initialize_app(self.cred) | ||
|
||
def login_for_access_token(self, username, password) -> Token: | ||
payload = { | ||
"email": username, | ||
"password": password.get_secret_value(), | ||
"returnSecureToken": True, | ||
} | ||
|
||
api_key = os.getenv("FIREBASE_API_KEY", "") | ||
# https://github.com/firebase/firebase-admin-python/blob/master/firebase_admin/_auth_utils.py | ||
id_toolkit = "identitytoolkit.googleapis.com/v1" | ||
sign_in_url_without_scheme = f"{id_toolkit}/accounts:signInWithPassword?key={api_key}" | ||
emulator_host = os.getenv("FIREBASE_AUTH_EMULATOR_HOST", "") | ||
if emulator_host != "": | ||
sign_in_url = f"http://{emulator_host}/{sign_in_url_without_scheme}" | ||
else: | ||
sign_in_url = f"https://{sign_in_url_without_scheme}" | ||
|
||
try: | ||
resp = requests.post( | ||
sign_in_url, | ||
json.dumps(payload), | ||
headers={"Content-Type": "application/json"}, | ||
timeout=30, | ||
) | ||
except requests.exceptions.Timeout as firebase_timeout: | ||
raise AuthException( | ||
AuthErrorType.INTERNAL_SERVER_ERROR, "Could not validate credentials" | ||
) from firebase_timeout | ||
|
||
data = resp.json() | ||
if not resp.ok: | ||
error_message = data["error"]["message"] | ||
raise AuthException( | ||
AuthErrorType.UNAUTHORIZED, | ||
error_message if error_message else "Could not validate credentials", | ||
) | ||
return Token( | ||
access_token=data["idToken"], token_type="bearer", refresh_token=data["refreshToken"] | ||
) | ||
|
||
def refresh_access_token(self, refresh_token) -> Token: | ||
payload = { | ||
"grant_type": "refresh_token", | ||
"refresh_token": refresh_token, | ||
} | ||
|
||
# see https://firebase.google.com/docs/reference/rest/auth#section-refresh-token | ||
api_key = os.environ["FIREBASE_API_KEY"] | ||
refresh_path = f"securetoken.googleapis.com/v1/token?key={api_key}" | ||
emulator_host = os.getenv("FIREBASE_AUTH_EMULATOR_HOST", "") | ||
if emulator_host != "": | ||
refresh_token_url = f"http://{emulator_host}/{refresh_path}" | ||
else: | ||
refresh_token_url = f"https://{refresh_path}" | ||
|
||
try: | ||
resp = requests.post( | ||
refresh_token_url, | ||
json.dumps(payload), | ||
headers={"Content-Type": "application/json"}, | ||
timeout=30, | ||
) | ||
except requests.exceptions.Timeout as firebase_timeout: | ||
raise AuthException( | ||
AuthErrorType.INTERNAL_SERVER_ERROR, | ||
"Could not refresh token", | ||
) from firebase_timeout | ||
|
||
data: dict = resp.json() | ||
if not resp.ok: | ||
error_message: str = data["error"]["message"] | ||
raise AuthException( | ||
AuthErrorType.UNAUTHORIZED, | ||
error_message if error_message else "Could not refresh token", | ||
) | ||
return Token( | ||
access_token=data["id_token"], | ||
token_type=data["token_type"], | ||
refresh_token=data["refresh_token"], | ||
) | ||
|
||
def check_and_get_user_info(self, token): | ||
super().check_token(token) | ||
try: | ||
decoded_token = auth.verify_id_token(token.credentials, check_revoked=True) | ||
except auth.ExpiredIdTokenError as error: | ||
raise AuthException( | ||
AuthErrorType.UNAUTHORIZED, | ||
"Token has expired", | ||
) from error | ||
except auth.RevokedIdTokenError as error: | ||
raise AuthException( | ||
AuthErrorType.UNAUTHORIZED, | ||
"Token has revoked", | ||
) from error | ||
except auth.CertificateFetchError as error: | ||
raise AuthException( | ||
AuthErrorType.SERVICE_UNAVAILABLE, | ||
"Failed to obtain required credentials", | ||
) from error | ||
except auth.UserDisabledError as error: | ||
raise AuthException( | ||
AuthErrorType.UNAUTHORIZED, | ||
"Disabled user", | ||
) from error | ||
except (auth.InvalidIdTokenError, ValueError) as error: | ||
raise AuthException( | ||
AuthErrorType.UNAUTHORIZED, | ||
"Could not validate credentials", | ||
) from error | ||
|
||
user_info = auth.get_user(decoded_token["uid"]) | ||
|
||
# check email verified if not using firebase emulator | ||
emulator_host = os.getenv("FIREBASE_AUTH_EMULATOR_HOST", "") | ||
if emulator_host == "" and user_info.email_verified is False: | ||
raise AuthException( | ||
AuthErrorType.UNAUTHORIZED, | ||
"Email is not verified. Try logging in on UI and verify email.", | ||
) | ||
|
||
email = user_info.email | ||
# if user_info.email is empty, get email from auth provider data | ||
if email is None: | ||
if len(user_info.provider_data) > 0: | ||
email = user_info.provider_data[0].email | ||
|
||
return user_info.uid, email |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
router以外で、HTTPExceptionはできれば避けたいが、現状HTTPBearerからトークンを取得して認証するので、HTTP専用の関数となっている。一旦このままでも問題なさそう