You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

407 lines
13 KiB

/**
* 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<String, Object> 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<String, Object> payload, String did) {
try {
// Phase 1: Simplified JWT implementation
// In production, this would use proper DID + cryptography libraries
// Create JWT header
Map<String, Object> 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<String, Object> map) {
StringBuilder json = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> 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);
}
}
}