Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import com.couchbase.admin.tokens.service.JwtTokenCacheService;

/**
* REST controller for Admin UI authentication
Expand Down Expand Up @@ -60,6 +63,9 @@ public class AuthController {
@Autowired
private InitializationService initializationService;

@Autowired
private JwtTokenCacheService jwtTokenCacheService;

@Value("${app.security.use-keycloak:false}")
private boolean useKeycloak;

Expand Down Expand Up @@ -368,16 +374,28 @@ private String issueAdminAccessToken(String email, String[] scopes) {
long hours = Long.parseLong(System.getProperty("oauth.token.expiry.hours",
System.getenv().getOrDefault("OAUTH_TOKEN_EXPIRY_HOURS", "24")));
java.time.Instant exp = now.plus(java.time.Duration.ofHours(hours));
String jti = UUID.randomUUID().toString();
JwtClaimsSet claims = JwtClaimsSet.builder()
.subject(email)
.issuedAt(now)
.expiresAt(exp)
.id(java.util.UUID.randomUUID().toString()) // Add JTI for revocation tracking
.id(jti) // Add JTI for revocation tracking
.claim("token_type", "admin") // Explicit token type (hardening)
.claim("scope", String.join(" ", scopes))
.claim("email", email)
.build();
return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();

String token = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();

try {
if (jwtTokenCacheService != null) {
jwtTokenCacheService.addToken(jti);
}
} catch (Exception e) {
logger.warn("⚠️ [LOGIN] Failed to add JTI to JwtTokenCacheService: {}", e.getMessage());
}

return token;
} catch (Exception e) {
logger.error("❌ Failed to issue admin access token: {}", e.getMessage());
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public class ConfigurationStartupService {

@Autowired
private JwtTokenCacheService jwtTokenCacheService;

@Autowired
private com.couchbase.admin.fhirBucket.service.FhirBucketService fhirBucketService;

@Autowired(required = false)
private com.couchbase.fhir.auth.AuthorizationServerConfig authorizationServerConfig;
Expand Down Expand Up @@ -266,6 +269,17 @@ public boolean loadConfigurationAndConnect() {
// Check initialization status for single-tenant "fhir" bucket
checkAndReportInitializationStatus();

// Ensure Admin collections exist (if missing) so admin seeding can proceed
try {
InitializationStatus status = initializationService.checkStatus(DEFAULT_CONNECTION_NAME);
if (status != null && !status.isAdminInitialized()) {
logger.info("ℹ️ Admin collections not present - ensuring Admin scope/collections now");
fhirBucketService.ensureAdminCollections(DEFAULT_CONNECTION_NAME, initializationService.getFhirBucketName());
}
} catch (Exception e) {
logger.debug("Could not ensure admin collections at startup: {}", e.getMessage());
}

// Attempt to seed initial Admin user (from config.yaml) when appropriate
seedAdminUserIfNeeded();

Expand Down Expand Up @@ -463,15 +477,15 @@ private void initializeTokenCache() {
*/
private void seedAdminUserIfNeeded() {
try {
// Only attempt seeding when the FHIR bucket is initialized and ready.
// Only attempt seeding when Admin collections exist (we don't require full FHIR initialization).
try {
InitializationStatus status = initializationService.checkStatus(DEFAULT_CONNECTION_NAME);
if (status == null || status.getStatus() != InitializationStatus.Status.READY) {
logger.info("ℹ️ Skipping admin seeding: FHIR initialization status is not READY (status={})", status == null ? "null" : status.getStatus());
if (status == null || !status.isAdminInitialized()) {
logger.info("ℹ️ Skipping admin seeding: Admin collections not present (status={})", status == null ? "null" : status.getStatus());
return;
}
} catch (Exception e) {
logger.warn("⚠️ Could not determine FHIR initialization status before seeding admin user: {}", e.getMessage());
logger.warn("⚠️ Could not determine admin initialization status before seeding admin user: {}", e.getMessage());
logger.debug("Initialization status check error:", e);
// If we cannot determine status, fail-safe: skip seeding to avoid exceptions during startup
return;
Expand Down
Loading