-
Notifications
You must be signed in to change notification settings - Fork 0
Auth 세션 인프라와 live 조립 추가 #12
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
9690f29
feat: Domain Auth 포트 추가
gnoes-ios 942f983
feat: Data Auth 세션 인프라 구현
gnoes-ios 0f2474c
feat: App AuthClient live 등록
gnoes-ios 721e84f
test: Auth Domain/Data 테스트 추가
gnoes-ios 8356ada
docs: Auth live 조립 규칙 문서 동기화
gnoes-ios e8e6936
fix: Auth storage/refresh 오류 정책 정리
gnoes-ios 3dd751e
test: Auth storage/refresh 실패 케이스 보강
gnoes-ios 693f99d
docs: Auth live 등록 흐름 문서 수정
gnoes-ios 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 hidden or 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 |
|---|---|---|
| @@ -1,12 +1,16 @@ | ||
| import Data | ||
| import Domain | ||
| import ThirdParty | ||
|
|
||
| enum Dependencies { | ||
| static func register( | ||
| _ values: inout DependencyValues, | ||
| infra: InfraContainer | ||
| ) { | ||
| // TODO: Domain Client factory 등록 | ||
| _ = infra | ||
| _ = values | ||
| values.authClient = .live( | ||
| baseURL: infra.configuration.baseURL, | ||
| keychain: infra.keychain, | ||
| oauthServices: OAuthServiceFactory.makeStub() | ||
| ) | ||
| } | ||
| } |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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,49 @@ | ||
| import CoreNetwork | ||
| import CoreStorage | ||
| import Domain | ||
| import Foundation | ||
|
|
||
| public extension AuthClient { | ||
| /// Auth live 조립. plain refresh 경로로 refresher를 먼저 만들어 순환 의존을 끊는다. | ||
| static func live( | ||
| baseURL: URL, | ||
| keychain: any KeychainStorage, | ||
| oauthServices: OAuthServices | ||
| ) -> AuthClient { | ||
| let networkConfiguration = NetworkConfiguration(baseURL: baseURL) | ||
| let plainNetworkClient = DefaultNetworkClient.plain( | ||
| configuration: networkConfiguration | ||
| ) | ||
| let local = AuthLocalDatasource(keychain: keychain) | ||
|
|
||
| // refresh는 plain only. logout 경로가 없어 authed 자리에 plain을 넣는다. | ||
| let refreshRemote = AuthRemoteDatasource( | ||
| plainClient: plainNetworkClient, | ||
| authedClient: plainNetworkClient | ||
| ) | ||
| let tokenRefresher = AuthTokenRefresher( | ||
| remote: refreshRemote, | ||
| local: local | ||
| ) | ||
| let authedNetworkClient = DefaultNetworkClient.authed( | ||
| configuration: networkConfiguration, | ||
| tokenProvider: local, | ||
| tokenRefresher: tokenRefresher | ||
| ) | ||
| let fullRemote = AuthRemoteDatasource( | ||
| plainClient: plainNetworkClient, | ||
| authedClient: authedNetworkClient | ||
| ) | ||
| let repository = AuthRepositoryImpl( | ||
| remote: fullRemote, | ||
| local: local | ||
| ) | ||
|
|
||
| return AuthClientFactory.make( | ||
| repository: repository, | ||
| credentialProvider: { provider in | ||
| try await oauthServices.service(for: provider).login() | ||
| } | ||
| ) | ||
| } | ||
| } | ||
This file contains hidden or 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,30 @@ | ||
| import Domain | ||
| import Foundation | ||
|
|
||
| public enum AuthClientFactory { | ||
| public static func make( | ||
| repository: AuthRepositoryImpl, | ||
| credentialProvider: @escaping @Sendable (AuthProvider) async throws -> String | ||
| ) -> AuthClient { | ||
| AuthClient( | ||
| restoreSession: { | ||
| try await repository.restoreSession() | ||
| }, | ||
| login: { provider in | ||
| let credential = try await credentialProvider(provider) | ||
| switch provider { | ||
| case .kakao: | ||
| return try await repository.loginWithKakao(accessToken: credential) | ||
| case .apple: | ||
| return try await repository.loginWithApple(identityToken: credential) | ||
| } | ||
|
gnoes-ios marked this conversation as resolved.
|
||
| }, | ||
| logout: { | ||
| try await repository.logout() | ||
| }, | ||
| currentSession: { | ||
| await repository.currentSession() | ||
| } | ||
| ) | ||
| } | ||
| } | ||
This file contains hidden or 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,5 @@ | ||
| import Foundation | ||
|
|
||
| struct AppleLoginRequestDTO: Encodable, Sendable { | ||
| let identityToken: String | ||
| } |
This file contains hidden or 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,6 @@ | ||
| import Foundation | ||
|
|
||
| struct DevLoginRequestDTO: Encodable, Sendable { | ||
| // 서버 계약이 빈 body 또는 고정 payload 면 그에 맞춤. | ||
| // OpenAPI 확인 불가 시 빈 object `{}` 로 보낸다. | ||
| } |
This file contains hidden or 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,5 @@ | ||
| import Foundation | ||
|
|
||
| struct KakaoLoginRequestDTO: Encodable, Sendable { | ||
| let accessToken: String | ||
| } |
This file contains hidden or 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,30 @@ | ||
| import Domain | ||
| import Foundation | ||
|
|
||
| public struct LoginResponseDTO: Decodable, Equatable, Sendable { | ||
| public let accessToken: String | ||
| public let refreshToken: String | ||
| public let isNewUser: Bool | ||
| public let profileCompleted: Bool | ||
|
|
||
| public init( | ||
| accessToken: String, | ||
| refreshToken: String, | ||
| isNewUser: Bool, | ||
| profileCompleted: Bool | ||
| ) { | ||
| self.accessToken = accessToken | ||
| self.refreshToken = refreshToken | ||
| self.isNewUser = isNewUser | ||
| self.profileCompleted = profileCompleted | ||
| } | ||
|
|
||
| public func toDomain() -> AuthSession { | ||
| AuthSession( | ||
| accessToken: accessToken, | ||
| refreshToken: refreshToken, | ||
| isNewUser: isNewUser, | ||
| profileCompleted: profileCompleted | ||
| ) | ||
| } | ||
| } |
This file contains hidden or 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,5 @@ | ||
| import Foundation | ||
|
|
||
| struct RefreshRequestDTO: Encodable, Sendable { | ||
| let refreshToken: String | ||
| } |
This file contains hidden or 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,21 @@ | ||
| import Domain | ||
| import Foundation | ||
|
|
||
| public struct TokenResponseDTO: Decodable, Equatable, Sendable { | ||
| public let accessToken: String | ||
| public let refreshToken: String | ||
|
|
||
| public init(accessToken: String, refreshToken: String) { | ||
| self.accessToken = accessToken | ||
| self.refreshToken = refreshToken | ||
| } | ||
|
|
||
| public func applying(to session: AuthSession) -> AuthSession { | ||
| AuthSession( | ||
| accessToken: accessToken, | ||
| refreshToken: refreshToken, | ||
| isNewUser: session.isNewUser, | ||
| profileCompleted: session.profileCompleted | ||
| ) | ||
| } | ||
| } |
30 changes: 30 additions & 0 deletions
30
Projects/Data/Sources/Auth/Datasource/AuthLocalDatasource.swift
This file contains hidden or 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,30 @@ | ||
| import CoreNetwork | ||
| import CoreStorage | ||
| import Domain | ||
| import Foundation | ||
|
|
||
| public struct AuthLocalDatasource: TokenProviding { | ||
| private let keychain: any KeychainStorage | ||
| private let sessionKey: String | ||
|
|
||
| public init(keychain: any KeychainStorage, sessionKey: String = "auth.session") { | ||
| self.keychain = keychain | ||
| self.sessionKey = sessionKey | ||
| } | ||
|
|
||
| public func save(_ session: AuthSession) async throws { | ||
| try await keychain.save(session, forKey: sessionKey) | ||
| } | ||
|
|
||
| public func load() async throws -> AuthSession? { | ||
| try await keychain.get(forKey: sessionKey) | ||
| } | ||
|
|
||
| public func clear() async throws { | ||
| try await keychain.delete(forKey: sessionKey) | ||
| } | ||
|
|
||
| public func accessToken() async throws -> String? { | ||
| try await load()?.accessToken | ||
| } | ||
| } |
43 changes: 43 additions & 0 deletions
43
Projects/Data/Sources/Auth/Datasource/AuthRemoteDatasource.swift
This file contains hidden or 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,43 @@ | ||
| import CoreNetwork | ||
| import Foundation | ||
|
|
||
| public struct AuthRemoteDatasource: Sendable { | ||
| private let plainClient: any NetworkClient | ||
| private let authedClient: any NetworkClient | ||
|
|
||
| public init( | ||
| plainClient: any NetworkClient, | ||
| authedClient: any NetworkClient | ||
| ) { | ||
| self.plainClient = plainClient | ||
| self.authedClient = authedClient | ||
| } | ||
|
|
||
| public func loginWithKakao(accessToken: String) async throws -> LoginResponseDTO { | ||
| let body = try makeEncoder().encode(KakaoLoginRequestDTO(accessToken: accessToken)) | ||
| return try await plainClient.request(AuthEndpoint.loginKakao(body)) | ||
| } | ||
|
|
||
| public func loginWithApple(identityToken: String) async throws -> LoginResponseDTO { | ||
| let body = try makeEncoder().encode(AppleLoginRequestDTO(identityToken: identityToken)) | ||
| return try await plainClient.request(AuthEndpoint.loginApple(body)) | ||
| } | ||
|
|
||
| public func loginWithDev() async throws -> LoginResponseDTO { | ||
| let body = try makeEncoder().encode(DevLoginRequestDTO()) | ||
| return try await plainClient.request(AuthEndpoint.loginDev(body)) | ||
| } | ||
|
|
||
| public func refresh(refreshToken: String) async throws -> TokenResponseDTO { | ||
| let body = try makeEncoder().encode(RefreshRequestDTO(refreshToken: refreshToken)) | ||
| return try await plainClient.request(AuthEndpoint.refresh(body)) | ||
| } | ||
|
|
||
| public func logout() async throws { | ||
| try await authedClient.request(AuthEndpoint.logout) | ||
| } | ||
|
gnoes-ios marked this conversation as resolved.
|
||
|
|
||
| private func makeEncoder() -> JSONEncoder { | ||
| NetworkJSONCoding.makeEncoder() | ||
| } | ||
| } | ||
This file contains hidden or 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,41 @@ | ||
| import CoreNetwork | ||
| import Foundation | ||
|
|
||
| enum AuthEndpoint: APIEndpoint { | ||
| case loginKakao(Data) | ||
| case loginApple(Data) | ||
| case loginDev(Data) | ||
| case refresh(Data) | ||
| case logout | ||
|
|
||
| var path: String { | ||
| switch self { | ||
| case .loginKakao: | ||
| return "/api/auth/login/kakao" | ||
| case .loginApple: | ||
| return "/api/auth/login/apple" | ||
| case .loginDev: | ||
| return "/api/auth/login/dev" | ||
| case .refresh: | ||
| return "/api/auth/refresh" | ||
| case .logout: | ||
| return "/api/auth/logout" | ||
| } | ||
| } | ||
|
|
||
| var method: HTTPMethod { | ||
| .post | ||
| } | ||
|
|
||
| var body: Data? { | ||
| switch self { | ||
| case let .loginKakao(body), | ||
| let .loginApple(body), | ||
| let .loginDev(body), | ||
| let .refresh(body): | ||
| return body | ||
| case .logout: | ||
| return nil | ||
| } | ||
| } | ||
| } |
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.
Uh oh!
There was an error while loading. Please reload this page.