/** * DailyNotificationJWTManager.java * * Android JWT Manager for TimeSafari authentication enhancement * Extends existing ETagManager infrastructure with DID-based JWT authentication * * @author Matthew Raymer * @version 1.0.0 * @created 2025-10-03 06:53:30 UTC */ package com.timesafari.dailynotification; import android.util.Log; import android.content.Context; import java.net.HttpURLConnection; import java.util.HashMap; import java.util.Map; import java.util.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; import java.nio.charset.StandardCharsets; /** * Manages JWT authentication for TimeSafari integration * * This class extends the existing ETagManager infrastructure by adding: * - DID-based JWT token generation * - Automatic JWT header injection into HTTP requests * - JWT token expiration management * - Integration with existing DailyNotificationETagManager * * Phase 1 Implementation: Extends existing DailyNotificationETagManager.java */ public class DailyNotificationJWTManager { // MARK: - Constants private static final String TAG = "DailyNotificationJWTManager"; // JWT Headers private static final String HEADER_AUTHORIZATION = "Authorization"; private static final String HEADER_CONTENT_TYPE = "Content-Type"; // JWT Configuration private static final int DEFAULT_JWT_EXPIRATION_SECONDS = 60; // JWT Algorithm (simplified for Phase 1) private static final String ALGORITHM = "HS256"; // MARK: - Properties private final DailyNotificationStorage storage; private final DailyNotificationETagManager eTagManager; // Current authentication state private String currentActiveDid; private String currentJWTToken; private long jwtExpirationTime; // Configuration private int jwtExpirationSeconds; // MARK: - Initialization /** * Constructor * * @param storage Storage instance for persistence * @param eTagManager ETagManager instance for HTTP enhancements */ public DailyNotificationJWTManager(DailyNotificationStorage storage, DailyNotificationETagManager eTagManager) { this.storage = storage; this.eTagManager = eTagManager; this.jwtExpirationSeconds = DEFAULT_JWT_EXPIRATION_SECONDS; Log.d(TAG, "JWTManager initialized with ETagManager integration"); } // MARK: - ActiveDid Management /** * Set the active DID for authentication * * @param activeDid The DID to use for JWT generation */ public void setActiveDid(String activeDid) { setActiveDid(activeDid, DEFAULT_JWT_EXPIRATION_SECONDS); } /** * Set the active DID for authentication with custom expiration * * @param activeDid The DID to use for JWT generation * @param expirationSeconds JWT expiration time in seconds */ public void setActiveDid(String activeDid, int expirationSeconds) { try { Log.d(TAG, "Setting activeDid: " + activeDid + " with " + expirationSeconds + "s expiration"); this.currentActiveDid = activeDid; this.jwtExpirationSeconds = expirationSeconds; // Generate new JWT token immediately generateAndCacheJWT(); Log.i(TAG, "ActiveDid set successfully"); } catch (Exception e) { Log.e(TAG, "Error setting activeDid", e); throw new RuntimeException("Failed to set activeDid", e); } } /** * Get the current active DID * * @return Current active DID or null if not set */ public String getCurrentActiveDid() { return currentActiveDid; } /** * Check if we have a valid active DID and JWT token * * @return true if authentication is ready */ public boolean isAuthenticated() { return currentActiveDid != null && currentJWTToken != null && !isTokenExpired(); } // MARK: - JWT Token Management /** * Generate JWT token for current activeDid * * @param expiresInSeconds Expiration time in seconds * @return Generated JWT token */ public String generateJWTForActiveDid(String activeDid, int expiresInSeconds) { try { Log.d(TAG, "Generating JWT for activeDid: " + activeDid); long currentTime = System.currentTimeMillis() / 1000; // Create JWT payload Map payload = new HashMap<>(); payload.put("exp", currentTime + expiresInSeconds); payload.put("iat", currentTime); payload.put("iss", activeDid); payload.put("aud", "timesafari.notifications"); payload.put("sub", activeDid); // Generate JWT token (simplified implementation for Phase 1) String jwt = signWithDID(payload, activeDid); Log.d(TAG, "JWT generated successfully"); return jwt; } catch (Exception e) { Log.e(TAG, "Error generating JWT", e); throw new RuntimeException("Failed to generate JWT", e); } } /** * Generate and cache JWT token for current activeDid */ private void generateAndCacheJWT() { if (currentActiveDid == null) { Log.w(TAG, "Cannot generate JWT: no activeDid set"); return; } try { currentJWTToken = generateJWTForActiveDid(currentActiveDid, jwtExpirationSeconds); jwtExpirationTime = System.currentTimeMillis() + (jwtExpirationSeconds * 1000L); Log.d(TAG, "JWT cached successfully, expires at: " + jwtExpirationTime); } catch (Exception e) { Log.e(TAG, "Error caching JWT", e); throw new RuntimeException("Failed to cache JWT", e); } } /** * Check if current JWT token is expired * * @return true if token is expired */ private boolean isTokenExpired() { return currentJWTToken == null || System.currentTimeMillis() >= jwtExpirationTime; } /** * Refresh JWT token if needed */ public void refreshJWTIfNeeded() { if (isTokenExpired()) { Log.d(TAG, "JWT token expired, refreshing"); generateAndCacheJWT(); } } /** * Get current valid JWT token (refreshes if needed) * * @return Current JWT token */ public String getCurrentJWTToken() { refreshJWTIfNeeded(); return currentJWTToken; } // MARK: - HTTP Client Enhancement /** * Enhance HTTP client with JWT authentication headers * * Extends existing DailyNotificationETagManager connection creation * * @param connection HTTP connection to enhance * @param activeDid DID for authentication (optional, uses current if null) */ public void enhanceHttpClientWithJWT(HttpURLConnection connection, String activeDid) { try { // Set activeDid if provided if (activeDid != null && !activeDid.equals(currentActiveDid)) { setActiveDid(activeDid); } // Ensure we have a valid token if (!isAuthenticated()) { throw new IllegalStateException("No valid authentication available"); } // Add JWT Authorization header String jwt = getCurrentJWTToken(); connection.setRequestProperty(HEADER_AUTHORIZATION, "Bearer " + jwt); // Set JSON content type for API requests connection.setRequestProperty(HEADER_CONTENT_TYPE, "application/json"); Log.d(TAG, "HTTP client enhanced with JWT authentication"); } catch (Exception e) { Log.e(TAG, "Error enhancing HTTP client with JWT", e); throw new RuntimeException("Failed to enhance HTTP client", e); } } /** * Enhance HTTP client with JWT authentication for current activeDid * * @param connection HTTP connection to enhance */ public void enhanceHttpClientWithJWT(HttpURLConnection connection) { enhanceHttpClientWithJWT(connection, null); } // MARK: - JWT Signing (Simplified for Phase 1) /** * Sign JWT payload with DID (simplified implementation) * * Phase 1: Basic implementation using DID-based signing * Later phases: Integrate with proper DID cryptography * * @param payload JWT payload * @param did DID for signing * @return Signed JWT token */ private String signWithDID(Map payload, String did) { try { // Phase 1: Simplified JWT implementation // In production, this would use proper DID + cryptography libraries // Create JWT header Map header = new HashMap<>(); header.put("alg", ALGORITHM); header.put("typ", "JWT"); // Encode header and payload StringBuilder jwtBuilder = new StringBuilder(); // Header jwtBuilder.append(base64UrlEncode(mapToJson(header))); jwtBuilder.append("."); // Payload jwtBuilder.append(base64UrlEncode(mapToJson(payload))); jwtBuilder.append("."); // Signature (simplified - would use proper DID signing) String signature = createSignature(jwtBuilder.toString(), did); jwtBuilder.append(signature); String jwt = jwtBuilder.toString(); Log.d(TAG, "JWT signed successfully (length: " + jwt.length() + ")"); return jwt; } catch (Exception e) { Log.e(TAG, "Error signing JWT", e); throw new RuntimeException("Failed to sign JWT", e); } } /** * Create JWT signature (simplified for Phase 1) * * @param data Data to sign * @param did DID for signature * @return Base64-encoded signature */ private String createSignature(String data, String did) throws Exception { // Phase 1: Simplified signature using DID hash // Production would use proper DID cryptographic signing MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest((data + ":" + did).getBytes(StandardCharsets.UTF_8)); return base64UrlEncode(hash); } /** * Convert map to JSON string (simplified) */ private String mapToJson(Map map) { StringBuilder json = new StringBuilder("{"); boolean first = true; for (Map.Entry entry : map.entrySet()) { if (!first) json.append(","); json.append("\"").append(entry.getKey()).append("\":"); Object value = entry.getValue(); if (value instanceof String) { json.append("\"").append(value).append("\""); } else { json.append(value); } first = false; } json.append("}"); return json.toString(); } /** * Base64 URL-safe encoding */ private String base64UrlEncode(byte[] data) { return Base64.getUrlEncoder() .withoutPadding() .encodeToString(data); } /** * Base64 URL-safe encoding for strings */ private String base64UrlEncode(String data) { return base64UrlEncode(data.getBytes(StandardCharsets.UTF_8)); } // MARK: - Testing and Debugging /** * Get current JWT token info for debugging * * @return Token information */ public String getTokenDebugInfo() { return String.format( "JWT Token Info - ActiveDID: %s, HasToken: %s, Expired: %s, ExpiresAt: %d", currentActiveDid, currentJWTToken != null, isTokenExpired(), jwtExpirationTime ); } /** * Clear authentication state */ public void clearAuthentication() { try { Log.d(TAG, "Clearing authentication state"); currentActiveDid = null; currentJWTToken = null; jwtExpirationTime = 0; Log.i(TAG, "Authentication state cleared"); } catch (Exception e) { Log.e(TAG, "Error clearing authentication", e); } } }