Skip to content

Commit d380921

Browse files
authored
Merge pull request #40 from Zerohertz/issue#36/feat/google
[Feat] Google OAuth
2 parents 41e26dd + 7c3e238 commit d380921

File tree

36 files changed

+986
-648
lines changed

36 files changed

+986
-648
lines changed

app/api/v1/endpoints/admin/users.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,28 @@
22
from fastapi import Depends, status
33
from fastapi.responses import JSONResponse
44

5-
from app.core.auth import AdminAuthDeps, GitHubOAuthDeps, PasswordOAuthDeps
5+
from app.core.auth import (
6+
AdminAuthDeps,
7+
GitHubOAuthDeps,
8+
GoogleOAuthDeps,
9+
PasswordOAuthDeps,
10+
)
611
from app.core.container import Container
712
from app.core.router import CoreAPIRouter
8-
from app.schemas.users import UserPatchRequest, UserRequest, UserResponse
13+
from app.schemas.users import UserOut, UserPasswordAdminRequest, UserRequest
14+
from app.services.auth import AuthService
915
from app.services.users import UserService
1016

1117
router = CoreAPIRouter(
1218
prefix="/user",
1319
tags=["admin"],
14-
dependencies=[AdminAuthDeps, PasswordOAuthDeps, GitHubOAuthDeps],
20+
dependencies=[AdminAuthDeps, PasswordOAuthDeps, GoogleOAuthDeps, GitHubOAuthDeps],
1521
)
1622

1723

1824
@router.get(
1925
"/",
20-
response_model=list[UserResponse],
26+
response_model=list[UserOut],
2127
response_class=JSONResponse,
2228
status_code=status.HTTP_200_OK,
2329
summary="",
@@ -32,7 +38,7 @@ async def get_users(
3238

3339
@router.get(
3440
"/{id}",
35-
response_model=UserResponse,
41+
response_model=UserOut,
3642
response_class=JSONResponse,
3743
status_code=status.HTTP_200_OK,
3844
summary="",
@@ -43,12 +49,12 @@ async def get_user(
4349
id: int,
4450
service: UserService = Depends(Provide[Container.user_service]),
4551
):
46-
return await service.get_by_id(id)
52+
return await service.get_by_id(id=id)
4753

4854

4955
@router.put(
5056
"/{id}",
51-
response_model=UserResponse,
57+
response_model=UserOut,
5258
response_class=JSONResponse,
5359
status_code=status.HTTP_200_OK,
5460
summary="",
@@ -57,15 +63,15 @@ async def get_user(
5763
@inject
5864
async def put_user(
5965
id: int,
60-
user: UserRequest,
66+
schema: UserRequest,
6167
service: UserService = Depends(Provide[Container.user_service]),
6268
):
63-
return await service.put_by_id(id=id, schema=user)
69+
return await service.put_by_id(id=id, schema=schema)
6470

6571

6672
@router.patch(
6773
"/{id}",
68-
response_model=UserResponse,
74+
response_model=UserOut,
6975
response_class=JSONResponse,
7076
status_code=status.HTTP_200_OK,
7177
summary="",
@@ -74,15 +80,15 @@ async def put_user(
7480
@inject
7581
async def patch_user(
7682
id: int,
77-
user: UserPatchRequest,
78-
service: UserService = Depends(Provide[Container.user_service]),
83+
schema: UserPasswordAdminRequest,
84+
service: AuthService = Depends(Provide[Container.auth_service]),
7985
):
80-
return await service.patch_by_id(id=id, schema=user)
86+
return await service.patch_password_by_id(user_id=id, schema=schema)
8187

8288

8389
@router.delete(
8490
"/{id}",
85-
response_model=UserResponse,
91+
response_model=UserOut,
8692
response_class=JSONResponse,
8793
status_code=status.HTTP_200_OK,
8894
summary="",
@@ -93,4 +99,4 @@ async def delete_user(
9399
id: int,
94100
service: UserService = Depends(Provide[Container.user_service]),
95101
):
96-
return await service.delete_by_id(id)
102+
return await service.delete_by_id(id=id)

app/api/v1/endpoints/auth.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,24 @@
44
from fastapi import Depends, Form, status
55
from fastapi.responses import JSONResponse
66

7-
from app.core.auth import GitHubOAuthDeps, PasswordOAuthDeps, UserAuthDeps
7+
from app.core.auth import (
8+
GitHubOAuthDeps,
9+
GoogleOAuthDeps,
10+
PasswordOAuthDeps,
11+
UserAuthDeps,
12+
)
813
from app.core.container import Container
914
from app.core.router import CoreAPIRouter
1015
from app.schemas.auth import (
1116
GitHubOAuthRequest,
17+
GoogleOAuthRequest,
1218
JwtToken,
1319
PasswordOAuthReigsterRequest,
1420
PasswordOAuthRequest,
1521
RefreshOAuthRequest,
1622
)
1723
from app.schemas.users import UserOut, UserResponse
18-
from app.services.users import UserService
24+
from app.services.auth import AuthService
1925

2026
router = CoreAPIRouter(prefix="/auth", tags=["auth"])
2127

@@ -32,7 +38,7 @@
3238
@inject
3339
async def refresh(
3440
request: Annotated[RefreshOAuthRequest, Form(...)],
35-
service: UserService = Depends(Provide[Container.user_service]),
41+
service: AuthService = Depends(Provide[Container.auth_service]),
3642
):
3743
return await service.refresh(request)
3844

@@ -49,7 +55,7 @@ async def refresh(
4955
@inject
5056
async def register_password(
5157
request: Annotated[PasswordOAuthReigsterRequest, Form(...)],
52-
service: UserService = Depends(Provide[Container.user_service]),
58+
service: AuthService = Depends(Provide[Container.auth_service]),
5359
):
5460
return await service.register(request)
5561

@@ -67,11 +73,28 @@ async def register_password(
6773
async def log_in_password(
6874
# NOTE: OAuth2PasswordRequestForm
6975
request: Annotated[PasswordOAuthRequest, Form(...)],
70-
service: UserService = Depends(Provide[Container.user_service]),
76+
service: AuthService = Depends(Provide[Container.auth_service]),
7177
):
7278
return await service.log_in_password(schema=request)
7379

7480

81+
@router.post(
82+
"/token/google",
83+
response_model=JwtToken,
84+
response_class=JSONResponse,
85+
status_code=status.HTTP_200_OK,
86+
summary="Obtain an access token via Google OAuth",
87+
description="- Authenticate using Google OAuth and receive an access token.<br/>\n"
88+
"- The client must provide an authorization code obtained from Google.",
89+
)
90+
@inject
91+
async def log_in_google(
92+
request: Annotated[GoogleOAuthRequest, Form()],
93+
service: AuthService = Depends(Provide[Container.auth_service]),
94+
):
95+
return await service.log_in_google(request)
96+
97+
7598
@router.post(
7699
"/token/github",
77100
response_model=JwtToken,
@@ -84,7 +107,7 @@ async def log_in_password(
84107
@inject
85108
async def log_in_github(
86109
request: Annotated[GitHubOAuthRequest, Form()],
87-
service: UserService = Depends(Provide[Container.user_service]),
110+
service: AuthService = Depends(Provide[Container.auth_service]),
88111
):
89112
return await service.log_in_github(request)
90113

@@ -94,7 +117,7 @@ async def log_in_github(
94117
response_model=UserOut,
95118
response_class=JSONResponse,
96119
status_code=status.HTTP_200_OK,
97-
dependencies=[PasswordOAuthDeps, GitHubOAuthDeps],
120+
dependencies=[PasswordOAuthDeps, GoogleOAuthDeps, GitHubOAuthDeps],
98121
summary="Retrieve the current authenticated user's information",
99122
description="- Returns the authenticated user's details based on the provided access token.</br>\n"
100123
"- Requires a valid token obtained via password authentication or GitHub OAuth.",

app/api/v1/endpoints/shields.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from datetime import datetime
22

3-
from fastapi import APIRouter
43
from fastapi.responses import JSONResponse
54

5+
from app.core.router import CoreAPIRouter
66
from app.schemas.shields import Shields
77
from app.utils.shields import dday
88

9-
router = APIRouter(prefix="/shields", tags=["shields.io"])
9+
router = CoreAPIRouter(prefix="/shields", tags=["shields.io"])
1010

1111

1212
@router.get(

app/api/v1/endpoints/users.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
from fastapi import Depends, status
55
from fastapi.responses import JSONResponse
66

7-
from app.core.auth import GitHubOAuthDeps, PasswordOAuthDeps, UserAuthDeps
7+
from app.core.auth import (
8+
GitHubOAuthDeps,
9+
GoogleOAuthDeps,
10+
PasswordOAuthDeps,
11+
UserAuthDeps,
12+
)
813
from app.core.container import Container
914
from app.core.router import CoreAPIRouter
10-
from app.schemas.users import UserOut, UserPatchRequest, UserRequest, UserResponse
15+
from app.schemas.users import UserOut, UserPasswordRequest, UserRequest, UserResponse
16+
from app.services.auth import AuthService
1117
from app.services.users import UserService
1218

1319
router = CoreAPIRouter(prefix="/user", tags=["user"])
@@ -18,7 +24,7 @@
1824
response_model=UserResponse,
1925
response_class=JSONResponse,
2026
status_code=status.HTTP_200_OK,
21-
dependencies=[PasswordOAuthDeps, GitHubOAuthDeps],
27+
dependencies=[PasswordOAuthDeps, GoogleOAuthDeps, GitHubOAuthDeps],
2228
summary="",
2329
description="",
2430
)
@@ -36,25 +42,25 @@ async def put_user(
3642
response_model=UserResponse,
3743
response_class=JSONResponse,
3844
status_code=status.HTTP_200_OK,
39-
dependencies=[PasswordOAuthDeps, GitHubOAuthDeps],
45+
dependencies=[PasswordOAuthDeps, GoogleOAuthDeps, GitHubOAuthDeps],
4046
summary="",
4147
description="",
4248
)
4349
@inject
4450
async def patch_user(
45-
schema: UserPatchRequest,
51+
schema: UserPasswordRequest,
4652
user: Annotated[UserOut, UserAuthDeps],
47-
service: UserService = Depends(Provide[Container.user_service]),
53+
service: AuthService = Depends(Provide[Container.auth_service]),
4854
):
49-
return await service.patch_by_id(id=user.id, schema=schema)
55+
return await service.patch_password_by_id(user_id=user.id, schema=schema)
5056

5157

5258
@router.delete(
5359
"/",
5460
response_model=UserResponse,
5561
response_class=JSONResponse,
5662
status_code=status.HTTP_200_OK,
57-
dependencies=[PasswordOAuthDeps, GitHubOAuthDeps],
63+
dependencies=[PasswordOAuthDeps, GoogleOAuthDeps, GitHubOAuthDeps],
5864
summary="",
5965
description="",
6066
)

app/core/auth.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Annotated, Optional
1+
from typing import Annotated
22

33
from dependency_injector.wiring import Provide, inject
44
from fastapi import Depends, HTTPException, Request
@@ -14,16 +14,16 @@
1414
from app.models.enums import Role
1515
from app.schemas.auth import JwtAccessToken
1616
from app.schemas.users import UserOut
17-
from app.services.users import UserService
17+
from app.services.auth import AuthService
1818

1919

2020
class JwtBearer(HTTPBearer):
2121
def __init__(
2222
self,
2323
*,
24-
bearerFormat: Optional[str] = None,
25-
scheme_name: Optional[str] = None,
26-
description: Optional[str] = None,
24+
bearerFormat: str | None = None,
25+
scheme_name: str | None = None,
26+
description: str | None = None,
2727
):
2828
super().__init__(
2929
bearerFormat=bearerFormat,
@@ -48,7 +48,7 @@ async def __call__(self, request: Request) -> str: # type: ignore[override]
4848
@inject
4949
async def get_current_user(
5050
access_token: Annotated[str, Depends(jwt_bearer)],
51-
service: UserService = Depends(Provide[Container.user_service]),
51+
service: AuthService = Depends(Provide[Container.auth_service]),
5252
) -> UserOut:
5353
schema = JwtAccessToken(access_token=access_token)
5454
return await service.verify(schema=schema)
@@ -70,6 +70,18 @@ async def get_admin_user(user: Annotated[UserOut, UserAuthDeps]) -> UserOut:
7070
tokenUrl=oauth_endpoints.PASSWORD, scheme_name="Password OAuth"
7171
)
7272
)
73+
GoogleOAuthDeps = Depends(
74+
OAuth2AuthorizationCodeBearer(
75+
authorizationUrl=f"https://accounts.google.com/o/oauth2/v2/auth?client_id={configs.GOOGLE_OAUTH_CLIENT_ID}",
76+
tokenUrl=oauth_endpoints.GOOGLE,
77+
refreshUrl=None,
78+
scheme_name="Google OAuth",
79+
scopes={
80+
"email": "Google Email",
81+
"profile": "Google Profile",
82+
},
83+
)
84+
)
7385
GitHubOAuthDeps = Depends(
7486
OAuth2AuthorizationCodeBearer(
7587
authorizationUrl=f"https://github.com/login/oauth/authorize?client_id={configs.GITHUB_OAUTH_CLIENT_ID}",

app/core/configs.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class Configs(BaseSettings):
3333
DB_TABLE_CREATE: bool = True
3434

3535
# --------- AUTH SETTINGS --------- #
36+
GOOGLE_OAUTH_CLIENT_ID: str
37+
GOOGLE_OAUTH_CLIENT_SECRET: str
3638
GITHUB_OAUTH_CLIENT_ID: str
3739
GITHUB_OAUTH_CLIENT_SECRET: str
3840
# openssl rand -hex 32
@@ -72,6 +74,7 @@ def DATABASE_URI(self) -> str:
7274

7375
class OAuthEndpoints(BaseSettings):
7476
PASSWORD: str = f"{configs.PREFIX}/v1/auth/token/password"
77+
GOOGLE: str = f"{configs.PREFIX}/v1/auth/token/google"
7578
GITHUB: str = f"{configs.PREFIX}/v1/auth/token/github"
7679

7780

app/core/container.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from dependency_injector.containers import DeclarativeContainer, WiringConfiguration
22
from dependency_injector.providers import Factory
33

4+
from app.repositories.auth import AuthRepository
45
from app.repositories.users import UserRepository
6+
from app.services.auth import AuthService, JwtService
7+
from app.services.security import CryptService
58
from app.services.users import UserService
69

710

@@ -16,5 +19,15 @@ class Container(DeclarativeContainer):
1619
)
1720

1821
user_repository = Factory(UserRepository)
22+
auth_repository = Factory(AuthRepository)
1923

24+
jwt_service = Factory(JwtService)
25+
crypt_service = Factory(CryptService)
2026
user_service = Factory(UserService, user_repository=user_repository)
27+
auth_service = Factory(
28+
AuthService,
29+
auth_repository=auth_repository,
30+
user_repository=user_repository,
31+
jwt_service=jwt_service,
32+
crypt_service=crypt_service,
33+
)

0 commit comments

Comments
 (0)