Skip to content

Commit 59fa102

Browse files
committed
feat: Google API 지원을 위한 Google OAuth2 라우트 추가
1 parent eaa5ae3 commit 59fa102

File tree

15 files changed

+227
-0
lines changed

15 files changed

+227
-0
lines changed

app/core/const/google_api.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
GOOGLE_OAUTH2_REDIRECT_PATH = "google/oauth2/redirect"
2+
GOOGLE_OAUTH2_AUTH_URI = "https://accounts.google.com/o/oauth2/auth"
3+
GOOGLE_OAUTH2_TOKEN_URI = "https://oauth2.googleapis.com/token" # nosec: B105

app/core/settings.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@
166166
"event.sponsor",
167167
"admin_api",
168168
"participant_portal_api",
169+
"external_api",
170+
"external_api.google_oauth2",
169171
# django-constance
170172
"constance",
171173
]
@@ -346,6 +348,7 @@
346348
)
347349

348350
# Frontend domain settings
351+
BACKEND_DOMAIN = env("BACKEND_DOMAIN", default="https://rest-api.pycon.kr")
349352
FRONTEND_DOMAIN = types.SimpleNamespace(
350353
main=env("FRONTEND_MAIN_URL", default="https://pycon.kr"),
351354
admin=env("FRONTEND_ADMIN_URL", default="https://admin.pycon.kr"),
@@ -371,6 +374,12 @@
371374
},
372375
}
373376

377+
GOOGLE_CLOUD = types.SimpleNamespace(
378+
CLIENT_ID=env("GOOGLE_OAUTH_CLIENT_ID", default=""),
379+
CLIENT_SECRET=env("GOOGLE_OAUTH_CLIENT_SECRET", default=""),
380+
SCOPES=env.list("GOOGLE_OAUTH_SCOPES", default=[]),
381+
)
382+
374383
# Sentry Settings
375384
if SENTRY_DSN := env("SENTRY_DSN", default=""):
376385
SENTRY_TRACES_SAMPLE_RATE = env.float("SENTRY_TRACES_SAMPLE_RATE", default=1.0)

app/core/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
path("participant-portal/", include("participant_portal_api.urls")),
3030
path("event/presentation/", include("event.presentation.urls")),
3131
path("event/sponsor/", include("event.sponsor.urls")),
32+
path("external-api/", include("external_api.urls")),
3233
]
3334

3435
urlpatterns = [

app/core/util/google_api.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from urllib.parse import urljoin
2+
3+
from core.const.google_api import GOOGLE_OAUTH2_AUTH_URI, GOOGLE_OAUTH2_REDIRECT_PATH, GOOGLE_OAUTH2_TOKEN_URI
4+
from django.conf import settings
5+
from google.oauth2.credentials import Credentials
6+
from google_auth_oauthlib.flow import Flow
7+
8+
9+
def create_oauth_flow() -> Flow | None:
10+
if not all(getattr(settings.GOOGLE_CLOUD, attr) for attr in ("CLIENT_ID", "CLIENT_SECRET", "SCOPES")):
11+
return None
12+
13+
return Flow.from_client_config(
14+
client_config={
15+
"web": {
16+
"auth_uri": GOOGLE_OAUTH2_AUTH_URI,
17+
"token_uri": GOOGLE_OAUTH2_TOKEN_URI,
18+
"client_id": settings.GOOGLE_CLOUD.CLIENT_ID,
19+
"client_secret": settings.GOOGLE_CLOUD.CLIENT_SECRET,
20+
},
21+
},
22+
scopes=settings.GOOGLE_CLOUD.SCOPES,
23+
redirect_uri=urljoin(settings.BACKEND_DOMAIN, GOOGLE_OAUTH2_REDIRECT_PATH),
24+
)
25+
26+
27+
def create_authorization_url(
28+
flow: Flow, prompt: str = "consent", access_type: str = "offline", include_granted_scopes: bool = False
29+
) -> str:
30+
return flow.authorization_url(
31+
prompt=prompt,
32+
access_type=access_type,
33+
include_granted_scopes="true" if include_granted_scopes else "false",
34+
)[0]
35+
36+
37+
def fetch_credentials(flow: Flow, code: str) -> Credentials:
38+
flow.fetch_token(code=code)
39+
return flow.credentials
40+
41+
42+
def create_credentials(refresh_token: str) -> Credentials:
43+
return Credentials.from_authorized_user_info(
44+
{
45+
"refresh_token": refresh_token,
46+
"client_id": settings.GOOGLE_CLOUD.CLIENT_ID,
47+
"client_secret": settings.GOOGLE_CLOUD.CLIENT_SECRET,
48+
}
49+
)

app/external_api/__init__.py

Whitespace-only changes.

app/external_api/apps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class ExternalAPIConfig(AppConfig):
5+
name = "external_api"

app/external_api/google_oauth2/__init__.py

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class GoogleOAuth2APIConfig(AppConfig):
5+
name = "external_api.google_oauth2"
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Generated by Django 5.2 on 2025-08-23 06:01
2+
3+
import uuid
4+
5+
import django.db.models.deletion
6+
from django.conf import settings
7+
from django.db import migrations, models
8+
9+
10+
class Migration(migrations.Migration):
11+
initial = True
12+
13+
dependencies = [
14+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15+
]
16+
17+
operations = [
18+
migrations.CreateModel(
19+
name="GoogleOAuth2",
20+
fields=[
21+
("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
22+
("created_at", models.DateTimeField(auto_now_add=True)),
23+
("updated_at", models.DateTimeField(auto_now=True)),
24+
("deleted_at", models.DateTimeField(blank=True, null=True)),
25+
("refresh_token", models.CharField(max_length=512)),
26+
(
27+
"created_by",
28+
models.ForeignKey(
29+
null=True,
30+
on_delete=django.db.models.deletion.PROTECT,
31+
related_name="%(class)s_created_by",
32+
to=settings.AUTH_USER_MODEL,
33+
),
34+
),
35+
(
36+
"deleted_by",
37+
models.ForeignKey(
38+
null=True,
39+
on_delete=django.db.models.deletion.PROTECT,
40+
related_name="%(class)s_deleted_by",
41+
to=settings.AUTH_USER_MODEL,
42+
),
43+
),
44+
(
45+
"updated_by",
46+
models.ForeignKey(
47+
null=True,
48+
on_delete=django.db.models.deletion.PROTECT,
49+
related_name="%(class)s_updated_by",
50+
to=settings.AUTH_USER_MODEL,
51+
),
52+
),
53+
],
54+
options={
55+
"abstract": False,
56+
},
57+
),
58+
]

app/external_api/google_oauth2/migrations/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)