Manos Chorianopoulos

add login request

...@@ -731,7 +731,73 @@ let networkStatus = WarplySDK.shared.getNetworkStatus() ...@@ -731,7 +731,73 @@ let networkStatus = WarplySDK.shared.getNetworkStatus()
731 731
732 ## 🔐 Authentication 732 ## 🔐 Authentication
733 733
734 -### User Login 734 +### DEI Login (Email-based Authentication) 🆕
735 +
736 +The DEI login method provides email-based authentication for DEI platform users. This method automatically handles JWT token extraction and secure storage.
737 +
738 +```swift
739 +// Completion handler approach
740 +WarplySDK.shared.deiLogin(
741 + email: "user@example.com",
742 + completion: { response in
743 + if let response = response, response.getStatus == 1 {
744 + print("DEI login successful")
745 + // User is now authenticated - proceed with authenticated operations
746 + // Tokens are automatically stored and managed by the SDK
747 + } else {
748 + print("DEI login failed")
749 + }
750 + },
751 + failureCallback: { errorCode in
752 + print("DEI login failed with error: \(errorCode)")
753 + // Handle specific error codes
754 + switch errorCode {
755 + case 401:
756 + print("Invalid email or authentication failed")
757 + case -1009:
758 + print("No internet connection")
759 + default:
760 + print("Unknown error occurred")
761 + }
762 + }
763 +)
764 +
765 +// Async/await approach (Recommended)
766 +Task {
767 + do {
768 + let response = try await WarplySDK.shared.deiLogin(email: "user@example.com")
769 + if response.getStatus == 1 {
770 + print("DEI login successful")
771 + // User is now authenticated - proceed with authenticated operations
772 + // Access other authenticated endpoints like getCoupons, getProfile, etc.
773 + } else {
774 + print("DEI login failed")
775 + }
776 + } catch let error as WarplyError {
777 + switch error {
778 + case .authenticationFailed:
779 + print("Invalid email or authentication failed")
780 + case .networkError:
781 + print("Network error occurred")
782 + case .noInternetConnection:
783 + print("No internet connection")
784 + default:
785 + print("DEI login error: \(error.localizedDescription)")
786 + }
787 + } catch {
788 + print("Unexpected error: \(error)")
789 + }
790 +}
791 +```
792 +
793 +**Key Features:**
794 +- ✅ **Email-based authentication** - No complex credentials required
795 +- ✅ **Automatic token management** - JWT tokens extracted and stored securely
796 +- ✅ **Future-ready design** - Handles refresh tokens (currently null, will be valid later)
797 +- ✅ **Comprehensive error handling** - Detailed error codes and messages
798 +- ✅ **Analytics integration** - Success/failure events tracked automatically
799 +
800 +### Traditional User Login
735 801
736 ```swift 802 ```swift
737 // Completion handler approach 803 // Completion handler approach
...@@ -757,6 +823,32 @@ Task { ...@@ -757,6 +823,32 @@ Task {
757 } 823 }
758 ``` 824 ```
759 825
826 +### Cosmote User Authentication
827 +
828 +```swift
829 +// For Cosmote-specific authentication
830 +WarplySDK.shared.getCosmoteUser(guid: "cosmote-user-guid") { response in
831 + if let response = response, response.getStatus == 1 {
832 + print("Cosmote user authenticated")
833 + // Tokens automatically stored
834 + } else {
835 + print("Cosmote authentication failed")
836 + }
837 +}
838 +
839 +// Async/await variant
840 +Task {
841 + do {
842 + let response = try await WarplySDK.shared.getCosmoteUser(guid: "cosmote-user-guid")
843 + if response.getStatus == 1 {
844 + print("Cosmote user authenticated")
845 + }
846 + } catch {
847 + print("Cosmote authentication error: \(error)")
848 + }
849 +}
850 +```
851 +
760 ### User Logout 852 ### User Logout
761 853
762 ```swift 854 ```swift
......
1 +# DEI Login Implementation Test
2 +
3 +## Implementation Summary
4 +
5 +The DEI login functionality has been successfully implemented with the following components:
6 +
7 +### 1. Endpoint Configuration (Endpoints.swift)
8 +- ✅ Added `deiLogin(email: String)` case to Endpoint enum
9 +- ✅ Configured path: `/partners/dei/app_login`
10 +- ✅ Configured method: POST
11 +- ✅ Configured parameters: `["email": email]`
12 +- ✅ Configured authentication: `.standard` (no authorization header)
13 +- ✅ Set `requiresAuthentication: false`
14 +
15 +### 2. Network Service Method (NetworkService.swift)
16 +- ✅ Added `deiLogin(email: String)` method in NetworkService
17 +- ✅ Implemented comprehensive error handling
18 +- ✅ Added response validation (status == 1)
19 +- ✅ Added token extraction and validation
20 +- ✅ Added automatic TokenModel creation and database storage
21 +- ✅ Added detailed logging for debugging
22 +
23 +### 3. Public SDK Methods (WarplySDK.swift)
24 +- ✅ Added completion handler variant: `deiLogin(email:completion:failureCallback:)`
25 +- ✅ Added async/await variant: `deiLogin(email:) async throws`
26 +- ✅ Added comprehensive documentation with examples
27 +- ✅ Added analytics events for success/failure tracking
28 +- ✅ Added state management (clears CCMS campaigns)
29 +- ✅ Added proper error handling and conversion
30 +
31 +## Key Features Implemented
32 +
33 +### Authentication Flow
34 +1. **Email-based authentication** - No authorization header required
35 +2. **JWT token extraction** - Extracts access_token and refresh_token from nested response
36 +3. **Automatic token storage** - Stores tokens in database using TokenModel
37 +4. **Future-ready design** - Handles null refresh tokens (will be valid later)
38 +
39 +### Error Handling
40 +1. **Status validation** - Ensures response status == 1
41 +2. **Token validation** - Ensures access_token exists and is not empty
42 +3. **Network error handling** - Comprehensive error mapping and logging
43 +4. **Analytics tracking** - Success/failure events for monitoring
44 +
45 +### Response Structure Support
46 +Expected response format:
47 +```json
48 +{
49 + "status": 1,
50 + "tokens": {
51 + "access_token": "eyJ...",
52 + "refresh_token": null,
53 + "client_id": "...",
54 + "client_secret": "..."
55 + }
56 +}
57 +```
58 +
59 +## Usage Examples
60 +
61 +### Completion Handler Variant
62 +```swift
63 +WarplySDK.shared.deiLogin(
64 + email: "user@example.com",
65 + completion: { response in
66 + if let response = response, response.getStatus == 1 {
67 + print("DEI login successful")
68 + // User is now authenticated - proceed with authenticated operations
69 + } else {
70 + print("DEI login failed")
71 + }
72 + },
73 + failureCallback: { errorCode in
74 + print("DEI login failed with error: \(errorCode)")
75 + }
76 +)
77 +```
78 +
79 +### Async/Await Variant
80 +```swift
81 +Task {
82 + do {
83 + let response = try await WarplySDK.shared.deiLogin(email: "user@example.com")
84 + if response.getStatus == 1 {
85 + print("DEI login successful")
86 + // User is now authenticated - proceed with authenticated operations
87 + } else {
88 + print("DEI login failed")
89 + }
90 + } catch {
91 + print("DEI login failed with error: \(error)")
92 + }
93 +}
94 +```
95 +
96 +## Integration with Existing Framework
97 +
98 +### Token Management
99 +- ✅ Integrates with existing TokenModel system
100 +- ✅ Integrates with DatabaseManager for secure storage
101 +- ✅ Integrates with TokenRefreshManager for future token refresh
102 +- ✅ Integrates with NetworkService for automatic token usage
103 +
104 +### Analytics
105 +- ✅ Follows existing analytics patterns
106 +- ✅ Posts success/failure events to Dynatrace
107 +- ✅ Uses existing event system (SwiftEventBus + EventDispatcher)
108 +
109 +### Error Handling
110 +- ✅ Uses existing WarplyError system
111 +- ✅ Follows existing error mapping patterns
112 +- ✅ Provides consistent error codes and messages
113 +
114 +## Testing Checklist
115 +
116 +### Basic Functionality
117 +- [ ] Test successful login with valid email
118 +- [ ] Test failed login with invalid email
119 +- [ ] Test network error handling
120 +- [ ] Test token extraction and storage
121 +- [ ] Test both completion handler and async/await variants
122 +
123 +### Integration Testing
124 +- [ ] Test that tokens are stored in database
125 +- [ ] Test that tokens are used for subsequent authenticated requests
126 +- [ ] Test analytics events are posted correctly
127 +- [ ] Test error handling and conversion
128 +
129 +### Edge Cases
130 +- [ ] Test with malformed response
131 +- [ ] Test with missing tokens in response
132 +- [ ] Test with null refresh token (current expected behavior)
133 +- [ ] Test network connectivity issues
134 +
135 +## Implementation Status: ✅ COMPLETE
136 +
137 +All required components have been implemented:
138 +1. ✅ Endpoint configuration
139 +2. ✅ Network service method
140 +3. ✅ Public SDK methods (both variants)
141 +4. ✅ Comprehensive documentation
142 +5. ✅ Error handling and analytics
143 +6. ✅ Integration with existing systems
144 +
145 +The DEI login functionality is ready for testing and integration.
This diff is collapsed. Click to expand it.
...@@ -2840,6 +2840,159 @@ public final class WarplySDK { ...@@ -2840,6 +2840,159 @@ public final class WarplySDK {
2840 } 2840 }
2841 } 2841 }
2842 2842
2843 + // MARK: - DEI Authentication
2844 +
2845 + /**
2846 + * DEI Login with automatic token handling
2847 + *
2848 + * This method authenticates a user with the DEI platform using their email address.
2849 + * Upon successful authentication, it automatically extracts and stores JWT tokens
2850 + * in the database for future authenticated requests.
2851 + *
2852 + * @param email User's email address for DEI authentication
2853 + * @param completion Completion handler with response model (nil on failure)
2854 + * @param failureCallback Failure callback with error code
2855 + *
2856 + * @discussion This method performs:
2857 + * - Email-based authentication with DEI platform
2858 + * - Automatic JWT token extraction and validation
2859 + * - Secure token storage in database using TokenModel
2860 + * - Comprehensive error handling and analytics
2861 + *
2862 + * @note The DEI login endpoint does not require authorization headers.
2863 + * Tokens are automatically stored and will be used by NetworkService for future requests.
2864 + *
2865 + * Error Scenarios:
2866 + * - Invalid email format: Server validation error
2867 + * - Network issues: Network error callback
2868 + * - Invalid response structure: Parsing error
2869 + * - Missing tokens: Authentication error
2870 + *
2871 + * @example
2872 + * ```swift
2873 + * WarplySDK.shared.deiLogin(
2874 + * email: "user@example.com",
2875 + * completion: { response in
2876 + * if let response = response, response.getStatus == 1 {
2877 + * print("DEI login successful")
2878 + * // User is now authenticated - proceed with authenticated operations
2879 + * } else {
2880 + * print("DEI login failed")
2881 + * }
2882 + * },
2883 + * failureCallback: { errorCode in
2884 + * print("DEI login failed with error: \(errorCode)")
2885 + * }
2886 + * )
2887 + * ```
2888 + */
2889 + public func deiLogin(email: String, completion: @escaping (VerifyTicketResponseModel?) -> Void, failureCallback: @escaping (Int) -> Void) {
2890 + // Clear previous state
2891 + setCCMSLoyaltyCampaigns(campaigns: [])
2892 +
2893 + Task {
2894 + do {
2895 + let response = try await networkService.deiLogin(email: email)
2896 + let tempResponse = VerifyTicketResponseModel(dictionary: response)
2897 +
2898 + await MainActor.run {
2899 + if tempResponse.getStatus == 1 {
2900 + print("✅ [WarplySDK] DEI login successful")
2901 +
2902 + // Analytics for successful login
2903 + let dynatraceEvent = LoyaltySDKDynatraceEventModel()
2904 + dynatraceEvent._eventName = "custom_success_dei_login_loyalty"
2905 + dynatraceEvent._parameters = nil
2906 + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent)
2907 +
2908 + // Tokens are already extracted and stored by NetworkService.deiLogin
2909 + print("✅ [WarplySDK] DEI tokens automatically stored by NetworkService")
2910 +
2911 + } else {
2912 + print("❌ [WarplySDK] DEI login failed - invalid status")
2913 +
2914 + // Analytics for failed login
2915 + let dynatraceEvent = LoyaltySDKDynatraceEventModel()
2916 + dynatraceEvent._eventName = "custom_error_dei_login_loyalty"
2917 + dynatraceEvent._parameters = nil
2918 + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent)
2919 + }
2920 +
2921 + completion(tempResponse)
2922 + }
2923 + } catch {
2924 + await MainActor.run {
2925 + print("❌ [WarplySDK] DEI login network error: \(error)")
2926 +
2927 + // Analytics for network error
2928 + let dynatraceEvent = LoyaltySDKDynatraceEventModel()
2929 + dynatraceEvent._eventName = "custom_error_dei_login_loyalty"
2930 + dynatraceEvent._parameters = nil
2931 + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent)
2932 +
2933 + self.handleError(error, context: "deiLogin", endpoint: "deiLogin", failureCallback: failureCallback)
2934 + }
2935 + }
2936 + }
2937 + }
2938 +
2939 + /**
2940 + * DEI Login with automatic token handling (async/await variant)
2941 + *
2942 + * This method authenticates a user with the DEI platform using their email address.
2943 + * Upon successful authentication, it automatically extracts and stores JWT tokens
2944 + * in the database for future authenticated requests.
2945 + *
2946 + * @param email User's email address for DEI authentication
2947 + * @returns VerifyTicketResponseModel containing authentication result
2948 + * @throws WarplyError if the request fails
2949 + *
2950 + * @discussion This method performs:
2951 + * - Email-based authentication with DEI platform
2952 + * - Automatic JWT token extraction and validation
2953 + * - Secure token storage in database using TokenModel
2954 + * - Comprehensive error handling and analytics
2955 + *
2956 + * @note The DEI login endpoint does not require authorization headers.
2957 + * Tokens are automatically stored and will be used by NetworkService for future requests.
2958 + *
2959 + * Error Scenarios:
2960 + * - Invalid email format: Server validation error
2961 + * - Network issues: WarplyError.networkError
2962 + * - Invalid response structure: WarplyError.dataParsingError
2963 + * - Missing tokens: WarplyError.authenticationFailed
2964 + *
2965 + * @example
2966 + * ```swift
2967 + * Task {
2968 + * do {
2969 + * let response = try await WarplySDK.shared.deiLogin(email: "user@example.com")
2970 + * if response.getStatus == 1 {
2971 + * print("DEI login successful")
2972 + * // User is now authenticated - proceed with authenticated operations
2973 + * } else {
2974 + * print("DEI login failed")
2975 + * }
2976 + * } catch {
2977 + * print("DEI login failed with error: \(error)")
2978 + * }
2979 + * }
2980 + * ```
2981 + */
2982 + public func deiLogin(email: String) async throws -> VerifyTicketResponseModel {
2983 + return try await withCheckedThrowingContinuation { continuation in
2984 + deiLogin(email: email, completion: { response in
2985 + if let response = response {
2986 + continuation.resume(returning: response)
2987 + } else {
2988 + continuation.resume(throwing: WarplyError.authenticationFailed)
2989 + }
2990 + }, failureCallback: { errorCode in
2991 + continuation.resume(throwing: WarplyError.unknownError(errorCode))
2992 + })
2993 + }
2994 + }
2995 +
2843 // MARK: - Push Notifications 2996 // MARK: - Push Notifications
2844 2997
2845 /// Handle notification 2998 /// Handle notification
......
...@@ -57,6 +57,7 @@ public enum Endpoint { ...@@ -57,6 +57,7 @@ public enum Endpoint {
57 case refreshToken(clientId: String, clientSecret: String, refreshToken: String) 57 case refreshToken(clientId: String, clientSecret: String, refreshToken: String)
58 case logout 58 case logout
59 case getCosmoteUser(guid: String) 59 case getCosmoteUser(guid: String)
60 + case deiLogin(email: String)
60 61
61 // Campaigns 62 // Campaigns
62 case getCampaigns(language: String, filters: [String: Any]) 63 case getCampaigns(language: String, filters: [String: Any])
...@@ -122,6 +123,8 @@ public enum Endpoint { ...@@ -122,6 +123,8 @@ public enum Endpoint {
122 return "/partners/cosmote/verify" 123 return "/partners/cosmote/verify"
123 case .getCosmoteUser: 124 case .getCosmoteUser:
124 return "/partners/oauth/{appUUID}/token" 125 return "/partners/oauth/{appUUID}/token"
126 + case .deiLogin:
127 + return "/partners/dei/app_login"
125 128
126 // Authentication endpoints 129 // Authentication endpoints
127 case .refreshToken: 130 case .refreshToken:
...@@ -163,7 +166,7 @@ public enum Endpoint { ...@@ -163,7 +166,7 @@ public enum Endpoint {
163 switch self { 166 switch self {
164 case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, 167 case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized,
165 .getCoupons, .getCouponSets, .getAvailableCoupons, 168 .getCoupons, .getCouponSets, .getAvailableCoupons,
166 - .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .getMerchants, .getMerchantCategories, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser: 169 + .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .getMerchants, .getMerchantCategories, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin:
167 return .POST 170 return .POST
168 case .getSingleCampaign, .getNetworkStatus: 171 case .getSingleCampaign, .getNetworkStatus:
169 return .GET 172 return .GET
...@@ -224,6 +227,12 @@ public enum Endpoint { ...@@ -224,6 +227,12 @@ public enum Endpoint {
224 "user_identifier": guid 227 "user_identifier": guid
225 ] 228 ]
226 229
230 + // DEI Login - simple email parameter structure
231 + case .deiLogin(let email):
232 + return [
233 + "email": email
234 + ]
235 +
227 // Campaign endpoints - nested structure with campaigns wrapper 236 // Campaign endpoints - nested structure with campaigns wrapper
228 case .getCampaigns(let language, let filters): 237 case .getCampaigns(let language, let filters):
229 return [ 238 return [
...@@ -447,7 +456,7 @@ public enum Endpoint { ...@@ -447,7 +456,7 @@ public enum Endpoint {
447 456
448 public var requiresAuthentication: Bool { 457 public var requiresAuthentication: Bool {
449 switch self { 458 switch self {
450 - case .register, .verifyTicket, .getCosmoteUser, .getNetworkStatus: 459 + case .register, .verifyTicket, .getCosmoteUser, .deiLogin, .getNetworkStatus:
451 return false 460 return false
452 default: 461 default:
453 return true 462 return true
...@@ -505,7 +514,7 @@ public enum Endpoint { ...@@ -505,7 +514,7 @@ public enum Endpoint {
505 // Standard Authentication (loyalty headers only) 514 // Standard Authentication (loyalty headers only)
506 case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout, 515 case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout,
507 .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo, 516 .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo,
508 - .getMerchants, .getMerchantCategories, .getArticles, .getNetworkStatus: 517 + .getMerchants, .getMerchantCategories, .getArticles, .getNetworkStatus, .deiLogin:
509 return .standard 518 return .standard
510 519
511 // Bearer Token Authentication (loyalty headers + Authorization: Bearer) 520 // Bearer Token Authentication (loyalty headers + Authorization: Bearer)
......
...@@ -962,6 +962,57 @@ extension NetworkService { ...@@ -962,6 +962,57 @@ extension NetworkService {
962 return response 962 return response
963 } 963 }
964 964
965 + // MARK: - DEI Authentication Methods
966 +
967 + /// DEI Login with automatic token handling
968 + /// - Parameter email: User's email address for DEI authentication
969 + /// - Returns: Response dictionary containing authentication result
970 + /// - Throws: NetworkError if request fails
971 + public func deiLogin(email: String) async throws -> [String: Any] {
972 + print("🔄 [NetworkService] DEI Login for email: \(email)")
973 + let endpoint = Endpoint.deiLogin(email: email)
974 + let response = try await requestRaw(endpoint)
975 +
976 + // Validate response status
977 + guard let status = response["status"] as? Int, status == 1 else {
978 + print("❌ [NetworkService] DEI Login failed - invalid status")
979 + throw NetworkError.serverError(400)
980 + }
981 +
982 + // Extract and validate tokens
983 + guard let tokens = response["tokens"] as? [String: Any],
984 + let accessToken = tokens["access_token"] as? String,
985 + !accessToken.isEmpty else {
986 + print("❌ [NetworkService] DEI Login failed - missing or empty access_token")
987 + throw NetworkError.invalidResponse
988 + }
989 +
990 + // Extract refresh token (may be null initially but prepare for future)
991 + let refreshToken = tokens["refresh_token"] as? String
992 +
993 + print("✅ [NetworkService] DEI Login successful - tokens received")
994 + print(" Access token: \(accessToken.prefix(10))...")
995 + print(" Refresh token: \(refreshToken?.prefix(10) ?? "null")...")
996 +
997 + // Create TokenModel and store in database
998 + let tokenModel = TokenModel(
999 + accessToken: accessToken,
1000 + refreshToken: refreshToken, // May be nil/null initially
1001 + clientId: tokens["client_id"] as? String,
1002 + clientSecret: tokens["client_secret"] as? String
1003 + )
1004 +
1005 + do {
1006 + try await DatabaseManager.shared.storeTokenModel(tokenModel)
1007 + print("✅ [NetworkService] DEI tokens stored in database successfully")
1008 + } catch {
1009 + print("❌ [NetworkService] Failed to store DEI tokens in database: \(error)")
1010 + // Don't throw - the login was successful, token storage is secondary
1011 + }
1012 +
1013 + return response
1014 + }
1015 +
965 // MARK: - Merchant Categories Methods 1016 // MARK: - Merchant Categories Methods
966 1017
967 /// Get merchant categories 1018 /// Get merchant categories
......