Skip to content

Commit dbbb1df

Browse files
committed
Merge branch 'kakao/WS-8'
2 parents d90da82 + 1c47ff9 commit dbbb1df

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2023
-85
lines changed

WAKe_server/middleware.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from django.core.handlers.wsgi import WSGIRequest
2+
from django.utils.deprecation import MiddlewareMixin
3+
from rest_framework.response import Response
4+
from rest_framework_simplejwt.tokens import SlidingToken
5+
6+
7+
class CustomAuthenticationMiddleware(MiddlewareMixin):
8+
9+
def set_jwt_cookie(self, request: WSGIRequest, response: Response):
10+
if request.user.is_authenticated:
11+
jwt = SlidingToken.for_user(request.user)
12+
origin = request.headers.get('origin')
13+
is_local = False if origin is None or 'localhost' not in origin else True
14+
domain = None if is_local else '.zps.kr'
15+
response.set_cookie(
16+
'jwt',
17+
str(jwt),
18+
max_age=3600 * 24 * 3,
19+
domain=domain,
20+
secure=(not is_local),
21+
httponly=True,
22+
samesite=False,
23+
)
24+
elif request.COOKIES.get('jwt', None) is not None:
25+
response.set_cookie(
26+
'jwt', max_age=0, domain='.zps.kr', secure=True,
27+
expires='Thu, 01 Jan 1970 00:00:00 GMT', samesite=False,
28+
)
29+
30+
return response
31+
32+
def process_response(self, request, response):
33+
return self.set_jwt_cookie(request, response)

WAKe_server/settings/base.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
For the full list of settings and their values, see
1010
https://docs.djangoproject.com/en/5.0/ref/settings/
1111
"""
12+
from datetime import timedelta
1213
from pathlib import Path
1314

1415
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -33,16 +34,23 @@
3334
"django.contrib.messages",
3435
"django.contrib.staticfiles",
3536
"corsheaders",
37+
"django.contrib.sites",
38+
"allauth",
39+
"allauth.account",
40+
"allauth.socialaccount",
41+
"allauth.socialaccount.providers.kakao",
3642
]
3743

3844
THIRD_PARTY_APPS = [
3945
"rest_framework",
46+
"rest_framework_simplejwt",
47+
"rest_framework_simplejwt.token_blacklist",
4048
"drf_yasg",
4149
"storages",
4250
]
4351

4452
PROJECT_APPS = [
45-
"users",
53+
"accounts",
4654
"records",
4755
]
4856

@@ -55,8 +63,10 @@
5563
"django.middleware.common.CommonMiddleware",
5664
"django.middleware.csrf.CsrfViewMiddleware",
5765
"django.contrib.auth.middleware.AuthenticationMiddleware",
66+
"WAKe_server.middleware.CustomAuthenticationMiddleware",
5867
"django.contrib.messages.middleware.MessageMiddleware",
5968
"django.middleware.clickjacking.XFrameOptionsMiddleware",
69+
"allauth.account.middleware.AccountMiddleware",
6070
]
6171

6272
TEMPLATES = [
@@ -115,3 +125,49 @@
115125

116126
CORS_ALLOW_CREDENTIALS = True
117127
CORS_ORIGIN_ALLOW_ALL = True
128+
129+
AUTHENTICATION_BACKENDS = [
130+
'django.contrib.auth.backends.ModelBackend',
131+
132+
'allauth.account.auth_backends.AuthenticationBackend',
133+
]
134+
135+
SITE_ID = 1
136+
137+
SOCIALACCOUNT_LOGIN_ON_GET = True
138+
LOGIN_REDIRECT_URL = 'main'
139+
ACCOUNT_LOGOUT_REDIRECT_URL = 'index'
140+
ACCOUNT_LOGOUT_ON_GET = True
141+
142+
AUTH_USER_MODEL = 'accounts.User'
143+
144+
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
145+
146+
REST_USE_JWT = True
147+
148+
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
149+
ACCOUNT_EMAIL_REQUIRED = True
150+
ACCOUNT_UNIQUE_EMAIL = True
151+
ACCOUNT_USERNAME_REQUIRED = False
152+
ACCOUNT_AUTHENTICATION_METHOD = 'email'
153+
ACCOUNT_EMAIL_VERIFICATION = 'none'
154+
155+
# jwt 토큰은 simplejwt의 JWTAuthentication으로 인증한다.
156+
REST_FRAMEWORK = {
157+
'DEFAULT_AUTHENTICATION_CLASSES': (
158+
'rest_framework_simplejwt.authentication.JWTAuthentication',
159+
),
160+
'DEFAULT_PERMISSION_CLASSES': (
161+
'rest_framework.permissions.AllowAny', # 누구나 접근
162+
),
163+
}
164+
165+
# 추가적인 JWT 설정
166+
SIMPLE_JWT = {
167+
'SLIDING_TOKEN_LIFETIME': timedelta(days=28),
168+
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=28),
169+
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
170+
'UPDATE_LAST_LOGIN': True,
171+
'USER_ID_FIELD': 'email',
172+
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.SlidingToken',)
173+
}

WAKe_server/settings/base_schema.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from drf_spectacular.openapi import AutoSchema
2+
from drf_spectacular.plumbing import ResolvedComponent, build_media_type_object, modify_media_types_for_versioning
3+
from drf_spectacular.utils import OpenApiResponse
4+
5+
from dtos import BaseDTO, ErrorStatus
6+
7+
8+
class DTOSchema(AutoSchema):
9+
def _get_response_bodies(self, direction='response'):
10+
response_serializers = self.get_response_serializers()
11+
12+
if hasattr(response_serializers, 'dto_name'):
13+
response_serializers: BaseDTO
14+
component = self.resolve_dto(response_serializers)
15+
response = {200: {'content': {'application/json': {'schema': component.ref}}}}
16+
return response
17+
elif isinstance(response_serializers, dict):
18+
responses = {}
19+
for code, serializer in response_serializers.items():
20+
if isinstance(code, tuple):
21+
code, media_types = str(code[0]), code[1:]
22+
else:
23+
code, media_types = str(code), None
24+
content_response = self._get_response_for_code(serializer, code, media_types, direction)
25+
if code in responses:
26+
responses[code]['content'].update(content_response['content'])
27+
else:
28+
responses[code] = content_response
29+
return responses
30+
31+
else:
32+
response = super()._get_response_bodies(direction)
33+
return response
34+
35+
def _get_response_for_code(self, serializer, status_code, media_types=None, direction='response'):
36+
if hasattr(serializer, 'dto_name'):
37+
if isinstance(serializer, OpenApiResponse):
38+
serializer, description, examples = (
39+
serializer.response, serializer.description, serializer.examples
40+
)
41+
else:
42+
description, examples = '', []
43+
44+
serializer: BaseDTO
45+
component = self.resolve_dto(serializer)
46+
headers = self._get_response_headers_for_code(status_code, direction)
47+
headers = {'headers': headers} if headers else {}
48+
49+
if not media_types:
50+
media_types = self.map_renderers('media_type')
51+
media_types = modify_media_types_for_versioning(self.view, media_types)
52+
53+
return {
54+
**headers,
55+
'content': {
56+
media_type: build_media_type_object(
57+
component.ref,
58+
self._get_examples(component, direction, media_type, status_code, examples)
59+
)
60+
for media_type in media_types
61+
},
62+
'description': description
63+
}
64+
elif isinstance(serializer, str):
65+
description, examples = '', []
66+
serializer: ErrorStatus
67+
headers = self._get_response_headers_for_code(status_code, direction)
68+
headers = {'headers': headers} if headers else {}
69+
70+
if not media_types:
71+
media_types = self.map_renderers('media_type')
72+
media_types = modify_media_types_for_versioning(self.view, media_types)
73+
74+
schema = {
75+
'type': 'object',
76+
'properties': {
77+
'status_code': {'type': 'integer'},
78+
'code': {'type': 'integer'},
79+
'errors': {'type': 'string'},
80+
},
81+
}
82+
83+
return {
84+
**headers,
85+
'content': {
86+
media_type: build_media_type_object(
87+
schema,
88+
self._get_examples(schema, direction, media_type, status_code, examples)
89+
)
90+
for media_type in media_types
91+
},
92+
'description': serializer
93+
}
94+
95+
else:
96+
super()._get_response_for_code(serializer, '200', media_types, direction)
97+
98+
def _get_request_body(self, direction='request'):
99+
if self.method not in ('PUT', 'PATCH', 'POST'):
100+
return None
101+
102+
request_serializers = self.get_request_serializer()
103+
104+
if hasattr(request_serializers, 'dto_name'):
105+
request_serializers: BaseDTO
106+
component = self.resolve_dto(request_serializers)
107+
request = {'content': {'application/json': {'schema': component.ref}}}
108+
return request
109+
else:
110+
request = super()._get_request_body(direction)
111+
return request
112+
113+
def resolve_dto(self, dto,) -> ResolvedComponent:
114+
component = ResolvedComponent(
115+
name=dto.dto_name,
116+
type=ResolvedComponent.SCHEMA,
117+
object=dto,
118+
)
119+
if component not in self.registry:
120+
dto.schema_render(self)
121+
122+
return self.registry[component]

WAKe_server/settings/dev.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,11 @@
2020
AWS_S3_HOST = 's3.%s.amazonaws.com' % AWS_REGION
2121
MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
2222

23-
STATIC_URL = "https://%s/static/" % AWS_S3_CUSTOM_DOMAIN
23+
STATIC_URL = "https://%s/static/" % AWS_S3_CUSTOM_DOMAIN
24+
25+
KAKAO_CLIENT_SECRET = get_secrets('KAKAO_CLIENT_SECRET')
26+
KAKAO_REST_API_KEY = get_secrets('KAKAO_REST_API_KEY')
27+
DJANGO_SECRET_KEY = get_secrets('DJANGO_SECRET_KEY')
28+
KAKAO_CALLBACK_URI = get_secrets('KAKAO_CALLBACK_URI')
29+
LOGIN_REDIRECT_URL = get_secrets('LOGIN_REDIRECT_URL')
30+
KAKAO_ADMIN_KEY = get_secrets('KAKAO_ADMIN_KEY')

WAKe_server/settings/local.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,41 @@
1616
AWS_S3_HOST = 's3.%s.amazonaws.com' % AWS_REGION
1717
MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
1818

19-
STATIC_URL = "https://%s/static/" % AWS_S3_CUSTOM_DOMAIN
19+
STATIC_URL = "https://%s/static/" % AWS_S3_CUSTOM_DOMAIN
20+
21+
KAKAO_CLIENT_SECRET = get_secrets('KAKAO_CLIENT_SECRET')
22+
KAKAO_REST_API_KEY = get_secrets('KAKAO_REST_API_KEY')
23+
KAKAO_ADMIN = get_secrets('KAKAO_ADMIN')
24+
25+
DJANGO_SECRET_KEY = get_secrets('DJANGO_SECRET_KEY')
26+
KAKAO_CALLBACK_URI = get_secrets('KAKAO_CALLBACK_URI')
27+
LOGIN_REDIRECT_URL = get_secrets('LOGIN_REDIRECT_URL')
28+
KAKAO_ADMIN_KEY = get_secrets('KAKAO_ADMIN_KEY')
29+
30+
SOCIALACCOUNT_PROVIDERS = {
31+
"kakao": {
32+
"APP": {
33+
"client_id": get_secrets('KAKAO_REST_API_KEY'),
34+
"secret": get_secrets('KAKAO_CLIENT_SECRET')
35+
}
36+
},
37+
38+
"apple": {
39+
"APP": {
40+
# Your service identifier.
41+
# todo apple login시에 알맞게 바꿔야함
42+
"client_id": "com.bunkerkids.shipdan.web,com.bunkerkids.shipdan",
43+
44+
# The Key ID (visible in the "View Key Details" page).
45+
"secret": get_secrets('APPLE_SECRET_KEY'),
46+
47+
# Member ID/App ID Prefix -- you can find it below your name
48+
# at the top right corner of the page, or it’s your App ID
49+
# Prefix in your App ID.
50+
"key": "V3ZYVUVY76",
51+
52+
# The certificate you downloaded when generating the key.
53+
"certificate_key": get_secrets('APPLE_CERTIFICATE_KEY').replace('\\n', '\n')
54+
}
55+
}
56+
}

WAKe_server/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
urlpatterns = [
2424
path("admin/", admin.site.urls),
2525

26+
path('api/accounts/', include('accounts.urls')),
27+
path('api/social/', include('social_app.urls', 'social')),
28+
path('api/allauth/', include('allauth.urls')),
29+
2630
path('api/records/', include('records.urls', 'records')),
2731
]
2832

File renamed without changes.

accounts/admin.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from django import forms
2+
from django.contrib import admin
3+
from django.contrib.auth.admin import UserAdmin
4+
from django.db.models import QuerySet
5+
from rest_framework_simplejwt.tokens import SlidingToken
6+
7+
from accounts.models import CommonProfile, User
8+
9+
10+
class CommonProfileAdmin(admin.ModelAdmin):
11+
list_display = ['id', 'name']
12+
13+
14+
admin.site.register(CommonProfile, CommonProfileAdmin)
15+
16+
17+
class UserCreationForm(forms.ModelForm):
18+
class Meta:
19+
model = User
20+
fields = ('email',)
21+
22+
def save(self, commit=True):
23+
# Save the provided password in hashed format
24+
user = super(UserCreationForm, self).save(commit=False)
25+
user.set_password(self.cleaned_data["password"])
26+
if commit:
27+
user.save()
28+
return user
29+
30+
31+
class UserAdmin(UserAdmin):
32+
add_form = UserCreationForm
33+
list_display = ['id', 'email', 'name', 'is_staff']
34+
ordering = ("-id",)
35+
search_fields = ('email', 'common_profile__name', )
36+
37+
fieldsets = (
38+
(None, {'fields': ('email', 'password', 'is_staff', 'is_superuser', 'groups', 'token')}),
39+
)
40+
add_fieldsets = (
41+
(None, {
42+
'classes': ('wide',),
43+
'fields': ('email', 'password', 'is_superuser', 'is_staff', 'is_active', 'groups')}
44+
),
45+
)
46+
readonly_fields = ('token',)
47+
48+
def token(self, obj: User):
49+
jwt_token = str(SlidingToken.for_user(obj))
50+
return jwt_token
51+
52+
def get_queryset(self, request):
53+
queryset: QuerySet = super().get_queryset(request)
54+
queryset = queryset.select_related('common_profile')
55+
return queryset
56+
57+
def name(self, obj):
58+
return obj.common_profile.name
59+
60+
61+
admin.site.register(User, UserAdmin)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.apps import AppConfig
22

33

4-
class UsersConfig(AppConfig):
4+
class AccountsConfig(AppConfig):
55
default_auto_field = "django.db.models.BigAutoField"
6-
name = "users"
6+
name = "accounts"

0 commit comments

Comments
 (0)