mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 01:28:03 +01:00
Add an endpoint to check if your backup-id can be rotated
Co-authored-by: Katherine <katherine@signal.org>
This commit is contained in:
@@ -11,6 +11,8 @@ import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -31,6 +33,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
@@ -134,6 +137,32 @@ public class BackupAuthManager {
|
||||
.toCompletableFuture();
|
||||
}
|
||||
|
||||
public record BackupIdRotationLimit(boolean hasPermitsRemaining, Duration nextPermitAvailable) {}
|
||||
|
||||
public CompletionStage<BackupIdRotationLimit> checkBackupIdRotationLimit(final Account account) {
|
||||
final RateLimiter messagesLimiter = rateLimiters.forDescriptor(RateLimiters.For.SET_BACKUP_ID);
|
||||
final RateLimiter mediaLimiter = rateLimiters.forDescriptor(RateLimiters.For.SET_PAID_MEDIA_BACKUP_ID);
|
||||
|
||||
final boolean isPaid = hasActiveVoucher(account);
|
||||
|
||||
final CompletionStage<Boolean> hasSetMessagesPermits =
|
||||
messagesLimiter.hasAvailablePermitsAsync(account.getUuid(), 1);
|
||||
final CompletionStage<Boolean> hasSetMediaPermits = isPaid
|
||||
? mediaLimiter.hasAvailablePermitsAsync(account.getUuid(), 1)
|
||||
: CompletableFuture.completedFuture(true);
|
||||
|
||||
return hasSetMessagesPermits.thenCombine(hasSetMediaPermits, (hasMessage, hasMedia) -> {
|
||||
if (hasMedia && hasMessage) {
|
||||
return new BackupIdRotationLimit(true, Duration.ZERO);
|
||||
} else {
|
||||
final Duration timeToNextPermit = Collections.max(Arrays.asList(
|
||||
messagesLimiter.config().permitRegenerationDuration(),
|
||||
isPaid ? mediaLimiter.config().permitRegenerationDuration() : Duration.ZERO));
|
||||
return new BackupIdRotationLimit(false, timeToNextPermit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public record Credential(BackupAuthCredentialResponse credential, Instant redemptionTime) {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -163,6 +163,34 @@ public class ArchiveController {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public record BackupIdLimitResponse(
|
||||
@Schema(description = "If true, a call to PUT /v1/archive/backupid may succeed without waiting")
|
||||
boolean hasPermitsRemaining,
|
||||
@Schema(description = "How long to wait before a permit becomes available, in seconds")
|
||||
long retryAfterSeconds) {}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Path("/backupid/limits")
|
||||
@Operation(
|
||||
summary = "Retrieve limits",
|
||||
description = """
|
||||
Determine whether the backup-id can currently be rotated
|
||||
""")
|
||||
@ApiResponse(responseCode = "200", description = "Successfully retrieved backup-id rotation limits", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "403", description = "Invalid account authentication")
|
||||
public CompletionStage<BackupIdLimitResponse> checkLimits(@Auth final AuthenticatedDevice authenticatedDevice) {
|
||||
return accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier())
|
||||
.thenCompose(maybeAccount -> {
|
||||
final Account account = maybeAccount
|
||||
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
|
||||
|
||||
return backupAuthManager.checkBackupIdRotationLimit(account).thenApply(limit ->
|
||||
new BackupIdLimitResponse(limit.hasPermitsRemaining(), limit.nextPermitAvailable().getSeconds()));
|
||||
});
|
||||
}
|
||||
|
||||
public record RedeemBackupReceiptRequest(
|
||||
@Schema(description = "Presentation of a ZK receipt encoded in standard padded base64", implementation = String.class)
|
||||
@JsonDeserialize(using = Deserializer.class)
|
||||
|
||||
Reference in New Issue
Block a user