From 13c0617701ebdb337a14dc13c54f1aa7adb9b766 Mon Sep 17 00:00:00 2001 From: litsynp Date: Mon, 11 Sep 2023 00:10:45 +0900 Subject: [PATCH] refactor: auth to domain and handler to main --- .../server/handler}/auth_handler.go | 27 ++++------- .../server/handler}/media_handler.go | 8 ++-- .../server/handler}/user_handler.go | 15 +++--- cmd/server/main.go | 3 +- {internal => cmd}/server/router.go | 38 ++++++++------- internal/domain/auth/context.go | 5 ++ internal/domain/auth/service.go | 46 +++++++++++++++++++ internal/domain/auth/view.go | 11 +++++ internal/server/auth.go | 38 --------------- internal/server/context.go | 5 -- lib/middleware/auth.go | 17 +++++++ pkg/docs/docs.go | 42 ++++++++--------- pkg/docs/swagger.json | 42 ++++++++--------- pkg/docs/swagger.yaml | 28 +++++------ swagger-gen.sh | 2 +- 15 files changed, 181 insertions(+), 146 deletions(-) rename {internal/server => cmd/server/handler}/auth_handler.go (69%) rename {internal/server => cmd/server/handler}/media_handler.go (92%) rename {internal/server => cmd/server/handler}/user_handler.go (90%) rename {internal => cmd}/server/router.go (70%) create mode 100644 internal/domain/auth/context.go create mode 100644 internal/domain/auth/service.go create mode 100644 internal/domain/auth/view.go delete mode 100644 internal/server/auth.go delete mode 100644 internal/server/context.go create mode 100644 lib/middleware/auth.go diff --git a/internal/server/auth_handler.go b/cmd/server/handler/auth_handler.go similarity index 69% rename from internal/server/auth_handler.go rename to cmd/server/handler/auth_handler.go index b37397d7..e137528c 100644 --- a/internal/server/auth_handler.go +++ b/cmd/server/handler/auth_handler.go @@ -1,23 +1,25 @@ -package server +package handler import ( "encoding/json" "fmt" "net/http" - "firebase.google.com/go/auth" "github.com/pet-sitter/pets-next-door-api/api/commonviews" "github.com/pet-sitter/pets-next-door-api/internal/configs" + "github.com/pet-sitter/pets-next-door-api/internal/domain/auth" "github.com/pet-sitter/pets-next-door-api/internal/domain/user" kakaoinfra "github.com/pet-sitter/pets-next-door-api/internal/infra/kakao" ) type authHandler struct { + authService auth.AuthService kakaoClient kakaoinfra.IKakaoClient } -func newAuthHandler(kakaoClient kakaoinfra.IKakaoClient) *authHandler { +func NewAuthHandler(authService auth.AuthService, kakaoClient kakaoinfra.IKakaoClient) *authHandler { return &authHandler{ + authService: authService, kakaoClient: kakaoClient, } } @@ -28,7 +30,7 @@ func newAuthHandler(kakaoClient kakaoinfra.IKakaoClient) *authHandler { // @Tags auth // @Success 302 // @Router /auth/login/kakao [get] -func (h *authHandler) kakaoLogin(w http.ResponseWriter, r *http.Request) { +func (h *authHandler) KakaoLogin(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "https://kauth.kakao.com/oauth/authorize?"+ "client_id="+configs.KakaoRestAPIKey+ "&redirect_uri="+configs.KakaoRedirectURI+ @@ -38,21 +40,13 @@ func (h *authHandler) kakaoLogin(w http.ResponseWriter, r *http.Request) { ) } -type kakaoCallbackResponse struct { - AuthToken string `json:"authToken"` - FirebaseProviderType user.FirebaseProviderType `json:"fbProviderType"` - FirebaseUID string `json:"fbUid"` - Email string `json:"email"` - PhotoURL string `json:"photoURL"` -} - // kakaoCallback godoc // @Summary Kakao 회원가입 콜백 API // @Description Kakao 로그인 콜백을 처리하고, 사용자 기본 정보와 함께 Firebase Custom Token을 발급합니다. // @Tags auth -// @Success 200 {object} kakaoCallbackResponse +// @Success 200 {object} auth.KakaoCallbackResponse // @Router /auth/callback/kakao [get] -func (h *authHandler) kakaoCallback(w http.ResponseWriter, r *http.Request) { +func (h *authHandler) KakaoCallback(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") tokenView, err := h.kakaoClient.FetchAccessToken(code) @@ -68,8 +62,7 @@ func (h *authHandler) kakaoCallback(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - authClient := ctx.Value(firebaseAuthClientKey).(*auth.Client) - customToken, err := authClient.CustomToken(ctx, fmt.Sprintf("%d", userProfile.ID)) + customToken, err := h.authService.CustomToken(ctx, fmt.Sprintf("%d", userProfile.ID)) if err != nil { commonviews.Unauthorized(w, nil, err.Error()) return @@ -77,7 +70,7 @@ func (h *authHandler) kakaoCallback(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(kakaoCallbackResponse{ + json.NewEncoder(w).Encode(auth.KakaoCallbackResponse{ AuthToken: customToken, FirebaseProviderType: user.FirebaseProviderTypeKakao, FirebaseUID: fmt.Sprintf("%d", userProfile.ID), diff --git a/internal/server/media_handler.go b/cmd/server/handler/media_handler.go similarity index 92% rename from internal/server/media_handler.go rename to cmd/server/handler/media_handler.go index a01679da..182cf36d 100644 --- a/internal/server/media_handler.go +++ b/cmd/server/handler/media_handler.go @@ -1,4 +1,4 @@ -package server +package handler import ( "net/http" @@ -13,7 +13,7 @@ type mediaHandler struct { mediaService media.MediaServicer } -func newMediaHandler(mediaService media.MediaServicer) *mediaHandler { +func NewMediaHandler(mediaService media.MediaServicer) *mediaHandler { return &mediaHandler{ mediaService: mediaService, } @@ -27,7 +27,7 @@ func newMediaHandler(mediaService media.MediaServicer) *mediaHandler { // @Param id path int true "미디어 ID" // @Success 200 {object} media.MediaView // @Router /media/{id} [get] -func (h *mediaHandler) findMediaByID(w http.ResponseWriter, r *http.Request) { +func (h *mediaHandler) FindMediaByID(w http.ResponseWriter, r *http.Request) { id, err := webutils.ParseIdFromPath(r, "id") if err != nil || id <= 0 { commonviews.NotFound(w, nil, "invalid media ID") @@ -57,7 +57,7 @@ func (h *mediaHandler) findMediaByID(w http.ResponseWriter, r *http.Request) { // @Param file formData file true "이미지 파일" // @Success 201 {object} media.MediaView // @Router /media/images [post] -func (h *mediaHandler) uploadImage(w http.ResponseWriter, r *http.Request) { +func (h *mediaHandler) UploadImage(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(10 << 20); err != nil { commonviews.BadRequest(w, nil, err.Error()) return diff --git a/internal/server/user_handler.go b/cmd/server/handler/user_handler.go similarity index 90% rename from internal/server/user_handler.go rename to cmd/server/handler/user_handler.go index 21bab33b..039d257a 100644 --- a/internal/server/user_handler.go +++ b/cmd/server/handler/user_handler.go @@ -1,4 +1,4 @@ -package server +package handler import ( "encoding/json" @@ -7,17 +7,20 @@ import ( "github.com/go-playground/validator" "github.com/pet-sitter/pets-next-door-api/api/commonviews" + "github.com/pet-sitter/pets-next-door-api/internal/domain/auth" "github.com/pet-sitter/pets-next-door-api/internal/domain/pet" "github.com/pet-sitter/pets-next-door-api/internal/domain/user" ) type UserHandler struct { userService user.UserServicer + authService auth.AuthService } -func newUserHandler(userService user.UserServicer) *UserHandler { +func NewUserHandler(userService user.UserServicer, authService auth.AuthService) *UserHandler { return &UserHandler{ userService: userService, + authService: authService, } } @@ -88,7 +91,7 @@ func (h *UserHandler) FindUserStatusByEmail(w http.ResponseWriter, r *http.Reque // @Success 200 {object} user.FindUserResponse // @Router /users/me [get] func (h *UserHandler) FindMyProfile(w http.ResponseWriter, r *http.Request) { - idToken, err := verifyAuth(r.Context(), r.Header.Get("Authorization")) + idToken, err := h.authService.VerifyAuth(r.Context(), r.Header.Get("Authorization")) if err != nil { commonviews.Unauthorized(w, nil, "unauthorized") log.Printf("verifyAuth error: %v\n", err) @@ -117,7 +120,7 @@ func (h *UserHandler) FindMyProfile(w http.ResponseWriter, r *http.Request) { // @Success 200 {object} user.UpdateUserResponse // @Router /users/me [put] func (h *UserHandler) UpdateMyProfile(w http.ResponseWriter, r *http.Request) { - idToken, err := verifyAuth(r.Context(), r.Header.Get("Authorization")) + idToken, err := h.authService.VerifyAuth(r.Context(), r.Header.Get("Authorization")) if err != nil { commonviews.Unauthorized(w, nil, "unauthorized") return @@ -158,7 +161,7 @@ func (h *UserHandler) UpdateMyProfile(w http.ResponseWriter, r *http.Request) { // @Success 200 // @Router /users/me/pets [put] func (h *UserHandler) AddMyPets(w http.ResponseWriter, r *http.Request) { - idToken, err := verifyAuth(r.Context(), r.Header.Get("Authorization")) + idToken, err := h.authService.VerifyAuth(r.Context(), r.Header.Get("Authorization")) if err != nil { commonviews.Unauthorized(w, nil, "unauthorized") return @@ -188,7 +191,7 @@ func (h *UserHandler) AddMyPets(w http.ResponseWriter, r *http.Request) { // @Success 200 {object} pet.FindMyPetsView // @Router /users/me/pets [get] func (h *UserHandler) FindMyPets(w http.ResponseWriter, r *http.Request) { - idToken, err := verifyAuth(r.Context(), r.Header.Get("Authorization")) + idToken, err := h.authService.VerifyAuth(r.Context(), r.Header.Get("Authorization")) if err != nil { commonviews.Unauthorized(w, nil, "unauthorized") return diff --git a/cmd/server/main.go b/cmd/server/main.go index b8607ecc..49f9cb56 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -6,7 +6,6 @@ import ( "github.com/pet-sitter/pets-next-door-api/internal/configs" firebaseinfra "github.com/pet-sitter/pets-next-door-api/internal/infra/firebase" - "github.com/pet-sitter/pets-next-door-api/internal/server" _ "github.com/pet-sitter/pets-next-door-api/pkg/docs" ) @@ -37,7 +36,7 @@ func main() { app = firebaseinfra.NewFirebaseAppFromCredentialsPath(configs.FirebaseCredentialsPath) } - r := server.NewRouter(app) + r := NewRouter(app) log.Printf("Starting server on port %s", configs.Port) log.Fatal(http.ListenAndServe(":"+configs.Port, r)) diff --git a/internal/server/router.go b/cmd/server/router.go similarity index 70% rename from internal/server/router.go rename to cmd/server/router.go index b39ae143..ff115f09 100644 --- a/internal/server/router.go +++ b/cmd/server/router.go @@ -1,4 +1,4 @@ -package server +package main import ( "context" @@ -8,7 +8,9 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" + "github.com/pet-sitter/pets-next-door-api/cmd/server/handler" "github.com/pet-sitter/pets-next-door-api/internal/configs" + "github.com/pet-sitter/pets-next-door-api/internal/domain/auth" "github.com/pet-sitter/pets-next-door-api/internal/domain/media" "github.com/pet-sitter/pets-next-door-api/internal/domain/user" "github.com/pet-sitter/pets-next-door-api/internal/infra/database" @@ -16,29 +18,31 @@ import ( kakaoinfra "github.com/pet-sitter/pets-next-door-api/internal/infra/kakao" s3infra "github.com/pet-sitter/pets-next-door-api/internal/infra/s3" "github.com/pet-sitter/pets-next-door-api/internal/postgres" + pndMiddleware "github.com/pet-sitter/pets-next-door-api/lib/middleware" httpSwagger "github.com/swaggo/http-swagger/v2" ) func NewRouter(app *firebaseinfra.FirebaseApp) *chi.Mux { r := chi.NewRouter() - registerMiddlewares(r, app) - addRoutes(r) - - return r -} - -func registerMiddlewares(r *chi.Mux, app *firebaseinfra.FirebaseApp) { authClient, err := app.Auth(context.Background()) + authService := auth.NewFirebaseBearerAuthService(authClient) if err != nil { log.Fatalf("error initializing app: %v\n", err) } + registerMiddlewares(r, app, authService) + addRoutes(r, authService) + + return r +} + +func registerMiddlewares(r *chi.Mux, app *firebaseinfra.FirebaseApp, authService auth.AuthService) { r.Use(middleware.Logger) - r.Use(buildFirebaseAuthMiddleware(authClient)) + r.Use(pndMiddleware.BuildAuthMiddleware(&authService, auth.FirebaseAuthClientKey)) } -func addRoutes(r *chi.Mux) { +func addRoutes(r *chi.Mux, authService auth.AuthService) { db, err := database.Open(configs.DatabaseURL) if err != nil { log.Fatalf("error opening database: %v\n", err) @@ -60,9 +64,9 @@ func addRoutes(r *chi.Mux) { mediaService, ) - authHandler := newAuthHandler(kakaoinfra.NewKakaoClient()) - userHandler := newUserHandler(userService) - mediaHandler := newMediaHandler(mediaService) + authHandler := handler.NewAuthHandler(authService, kakaoinfra.NewKakaoClient()) + userHandler := handler.NewUserHandler(userService, authService) + mediaHandler := handler.NewMediaHandler(mediaService) r.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -75,12 +79,12 @@ func addRoutes(r *chi.Mux) { r.Route("/api", func(r chi.Router) { r.Route("/auth", func(r chi.Router) { - r.Get("/login/kakao", authHandler.kakaoLogin) - r.Get("/callback/kakao", authHandler.kakaoCallback) + r.Get("/login/kakao", authHandler.KakaoLogin) + r.Get("/callback/kakao", authHandler.KakaoCallback) }) r.Route("/media", func(r chi.Router) { - r.Get("/{id}", mediaHandler.findMediaByID) - r.Post("/images", mediaHandler.uploadImage) + r.Get("/{id}", mediaHandler.FindMediaByID) + r.Post("/images", mediaHandler.UploadImage) }) r.Route("/users", func(r chi.Router) { r.Post("/", userHandler.RegisterUser) diff --git a/internal/domain/auth/context.go b/internal/domain/auth/context.go new file mode 100644 index 00000000..7a175f1b --- /dev/null +++ b/internal/domain/auth/context.go @@ -0,0 +1,5 @@ +package auth + +type ContextKey string + +const FirebaseAuthClientKey ContextKey = "firebaseAuthClient" diff --git a/internal/domain/auth/service.go b/internal/domain/auth/service.go new file mode 100644 index 00000000..8b1c4080 --- /dev/null +++ b/internal/domain/auth/service.go @@ -0,0 +1,46 @@ +package auth + +import ( + "context" + "fmt" + "strings" + + "firebase.google.com/go/auth" +) + +type AuthService interface { + VerifyAuth(ctx context.Context, authHeader string) (*auth.Token, error) + CustomToken(ctx context.Context, uid string) (string, error) +} + +type FirebaseBearerAuthService struct { + authClient *auth.Client +} + +func NewFirebaseBearerAuthService(authClient *auth.Client) *FirebaseBearerAuthService { + return &FirebaseBearerAuthService{ + authClient: authClient, + } +} + +func (s *FirebaseBearerAuthService) VerifyAuth(ctx context.Context, authHeader string) (*auth.Token, error) { + idToken, err := s.stripBearerToken(authHeader) + if err != nil { + return nil, err + } + + authToken, err := s.authClient.VerifyIDToken(ctx, idToken) + return authToken, err +} + +func (s *FirebaseBearerAuthService) CustomToken(ctx context.Context, uid string) (string, error) { + return s.authClient.CustomToken(ctx, uid) +} + +func (s *FirebaseBearerAuthService) stripBearerToken(authHeader string) (string, error) { + if len(authHeader) > 6 && strings.ToUpper(authHeader[0:7]) == "BEARER " { + return authHeader[7:], nil + } + + return authHeader, fmt.Errorf("invalid auth header") +} diff --git a/internal/domain/auth/view.go b/internal/domain/auth/view.go new file mode 100644 index 00000000..d610416c --- /dev/null +++ b/internal/domain/auth/view.go @@ -0,0 +1,11 @@ +package auth + +import "github.com/pet-sitter/pets-next-door-api/internal/domain/user" + +type KakaoCallbackResponse struct { + AuthToken string `json:"authToken"` + FirebaseProviderType user.FirebaseProviderType `json:"fbProviderType"` + FirebaseUID string `json:"fbUid"` + Email string `json:"email"` + PhotoURL string `json:"photoURL"` +} diff --git a/internal/server/auth.go b/internal/server/auth.go deleted file mode 100644 index dd7c1152..00000000 --- a/internal/server/auth.go +++ /dev/null @@ -1,38 +0,0 @@ -package server - -import ( - "context" - "fmt" - "net/http" - "strings" - - "firebase.google.com/go/auth" -) - -func buildFirebaseAuthMiddleware(app *auth.Client) func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := context.WithValue(r.Context(), firebaseAuthClientKey, app) - next.ServeHTTP(w, r.WithContext(ctx)) - }) - } -} - -func verifyAuth(ctx context.Context, authHeader string) (*auth.Token, error) { - authClient := ctx.Value(firebaseAuthClientKey).(*auth.Client) - idToken, err := stripBearerToken(authHeader) - if err != nil { - return nil, err - } - - authToken, err := authClient.VerifyIDToken(ctx, idToken) - return authToken, err -} - -func stripBearerToken(authHeader string) (string, error) { - if len(authHeader) > 6 && strings.ToUpper(authHeader[0:7]) == "BEARER " { - return authHeader[7:], nil - } - - return authHeader, fmt.Errorf("invalid auth header") -} diff --git a/internal/server/context.go b/internal/server/context.go deleted file mode 100644 index ae83c923..00000000 --- a/internal/server/context.go +++ /dev/null @@ -1,5 +0,0 @@ -package server - -type contextKey string - -const firebaseAuthClientKey contextKey = "firebaseAuthClient" diff --git a/lib/middleware/auth.go b/lib/middleware/auth.go new file mode 100644 index 00000000..98f92256 --- /dev/null +++ b/lib/middleware/auth.go @@ -0,0 +1,17 @@ +package middleware + +import ( + "context" + "net/http" + + "github.com/pet-sitter/pets-next-door-api/internal/domain/auth" +) + +func BuildAuthMiddleware(app *auth.AuthService, authKey auth.ContextKey) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), authKey, app) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} diff --git a/pkg/docs/docs.go b/pkg/docs/docs.go index edfa5d08..9b5d594e 100644 --- a/pkg/docs/docs.go +++ b/pkg/docs/docs.go @@ -35,7 +35,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/server.kakaoCallbackResponse" + "$ref": "#/definitions/auth.KakaoCallbackResponse" } } } @@ -300,6 +300,26 @@ const docTemplate = `{ } }, "definitions": { + "auth.KakaoCallbackResponse": { + "type": "object", + "properties": { + "authToken": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fbProviderType": { + "$ref": "#/definitions/user.FirebaseProviderType" + }, + "fbUid": { + "type": "string" + }, + "photoURL": { + "type": "string" + } + } + }, "media.MediaType": { "type": "string", "enum": [ @@ -453,26 +473,6 @@ const docTemplate = `{ } } }, - "server.kakaoCallbackResponse": { - "type": "object", - "properties": { - "authToken": { - "type": "string" - }, - "email": { - "type": "string" - }, - "fbProviderType": { - "$ref": "#/definitions/user.FirebaseProviderType" - }, - "fbUid": { - "type": "string" - }, - "photoURL": { - "type": "string" - } - } - }, "user.FindUserResponse": { "type": "object", "properties": { diff --git a/pkg/docs/swagger.json b/pkg/docs/swagger.json index 6de2a709..d91f32da 100644 --- a/pkg/docs/swagger.json +++ b/pkg/docs/swagger.json @@ -27,7 +27,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/server.kakaoCallbackResponse" + "$ref": "#/definitions/auth.KakaoCallbackResponse" } } } @@ -292,6 +292,26 @@ } }, "definitions": { + "auth.KakaoCallbackResponse": { + "type": "object", + "properties": { + "authToken": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fbProviderType": { + "$ref": "#/definitions/user.FirebaseProviderType" + }, + "fbUid": { + "type": "string" + }, + "photoURL": { + "type": "string" + } + } + }, "media.MediaType": { "type": "string", "enum": [ @@ -445,26 +465,6 @@ } } }, - "server.kakaoCallbackResponse": { - "type": "object", - "properties": { - "authToken": { - "type": "string" - }, - "email": { - "type": "string" - }, - "fbProviderType": { - "$ref": "#/definitions/user.FirebaseProviderType" - }, - "fbUid": { - "type": "string" - }, - "photoURL": { - "type": "string" - } - } - }, "user.FindUserResponse": { "type": "object", "properties": { diff --git a/pkg/docs/swagger.yaml b/pkg/docs/swagger.yaml index 555a13e3..80d5770d 100644 --- a/pkg/docs/swagger.yaml +++ b/pkg/docs/swagger.yaml @@ -1,5 +1,18 @@ basePath: /api definitions: + auth.KakaoCallbackResponse: + properties: + authToken: + type: string + email: + type: string + fbProviderType: + $ref: '#/definitions/user.FirebaseProviderType' + fbUid: + type: string + photoURL: + type: string + type: object media.MediaType: enum: - image @@ -101,19 +114,6 @@ definitions: weight_in_kg: type: number type: object - server.kakaoCallbackResponse: - properties: - authToken: - type: string - email: - type: string - fbProviderType: - $ref: '#/definitions/user.FirebaseProviderType' - fbUid: - type: string - photoURL: - type: string - type: object user.FindUserResponse: properties: email: @@ -247,7 +247,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/server.kakaoCallbackResponse' + $ref: '#/definitions/auth.KakaoCallbackResponse' summary: Kakao 회원가입 콜백 API tags: - auth diff --git a/swagger-gen.sh b/swagger-gen.sh index 63cc0e55..e9218b5d 100644 --- a/swagger-gen.sh +++ b/swagger-gen.sh @@ -1,2 +1,2 @@ #!/bin/bash -swag init -d ./cmd/server,./internal/server -o ./pkg/docs --pd +swag init -d ./cmd/server -o ./pkg/docs --pd