OpenAPI spec for VerificationController endpoints
This commit is contained in:
parent
8280106493
commit
0d412c88fd
|
@ -15,6 +15,13 @@ import io.grpc.StatusRuntimeException;
|
|||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.headers.Header;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
|
@ -68,6 +75,7 @@ import org.whispersystems.textsecuregcm.entities.VerificationCodeRequest;
|
|||
import org.whispersystems.textsecuregcm.entities.VerificationSessionResponse;
|
||||
import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.mappers.RegistrationServiceSenderExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
|
||||
import org.whispersystems.textsecuregcm.push.PushNotification;
|
||||
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
|
||||
|
@ -152,7 +160,19 @@ public class VerificationController {
|
|||
@Path("/session")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public VerificationSessionResponse createSession(@NotNull @Valid CreateVerificationSessionRequest request)
|
||||
@Operation(
|
||||
summary = "Creates a new verification session for a specific phone number",
|
||||
description = """
|
||||
Initiates a session to be able to verify the phone number for account registration. Check the response and
|
||||
submit requested information at PATCH /session/{sessionId}
|
||||
""")
|
||||
@ApiResponse(responseCode = "200", description = "The verification session was created successfully", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "422", description = "The request did not pass validation")
|
||||
@ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
|
||||
name = "Retry-After",
|
||||
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed",
|
||||
schema = @Schema(implementation = Integer.class)))
|
||||
public VerificationSessionResponse createSession(@NotNull @Valid final CreateVerificationSessionRequest request)
|
||||
throws RateLimitExceededException {
|
||||
|
||||
final Pair<String, PushNotification.TokenType> pushTokenAndType = validateAndExtractPushToken(
|
||||
|
@ -200,12 +220,28 @@ public class VerificationController {
|
|||
@Path("/session/{sessionId}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "Update a registration verification session",
|
||||
description = """
|
||||
Updates the session with requested information like an answer to a push challenge or captcha.
|
||||
If `requestedInformation` in the response is empty, and `allowedToRequestCode` is `true`, proceed to call
|
||||
`POST /session/{sessionId}/code`. If `requestedInformation` is empty and `allowedToRequestCode` is `false`,
|
||||
then the caller must create a new verification session.
|
||||
""")
|
||||
@ApiResponse(responseCode = "200", description = "Session was updated successfully with the information provided", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "403", description = "The information provided was not accepted (e.g push challenge or captcha verification failed)")
|
||||
@ApiResponse(responseCode = "422", description = "The request did not pass validation")
|
||||
@ApiResponse(responseCode = "429", description = "Too many attempts",
|
||||
content = @Content(schema = @Schema(implementation = VerificationSessionResponse.class)),
|
||||
headers = @Header(
|
||||
name = "Retry-After",
|
||||
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed",
|
||||
schema = @Schema(implementation = Integer.class)))
|
||||
public VerificationSessionResponse updateSession(
|
||||
@PathParam("sessionId") final String encodedSessionId,
|
||||
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent,
|
||||
@Context ContainerRequestContext requestContext,
|
||||
@NotNull @Valid final UpdateVerificationSessionRequest updateVerificationSessionRequest,
|
||||
@Context ContainerRequestContext context) {
|
||||
@Context final ContainerRequestContext requestContext,
|
||||
@NotNull @Valid final UpdateVerificationSessionRequest updateVerificationSessionRequest) {
|
||||
|
||||
final String sourceHost = (String) requestContext.getProperty(RemoteAddressFilter.REMOTE_ADDRESS_ATTRIBUTE_NAME);
|
||||
|
||||
|
@ -216,7 +252,7 @@ public class VerificationController {
|
|||
VerificationSession verificationSession = retrieveVerificationSession(registrationServiceSession);
|
||||
|
||||
final VerificationCheck verificationCheck = registrationFraudChecker.checkVerificationAttempt(
|
||||
context,
|
||||
requestContext,
|
||||
verificationSession,
|
||||
registrationServiceSession.number(),
|
||||
updateVerificationSessionRequest);
|
||||
|
@ -433,6 +469,15 @@ public class VerificationController {
|
|||
@GET
|
||||
@Path("/session/{sessionId}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "Get a registration verification session",
|
||||
description = """
|
||||
Retrieve metadata of the registration verification session with the specified ID
|
||||
""")
|
||||
@ApiResponse(responseCode = "200", description = "Session was retrieved successfully", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "400", description = "Invalid session ID")
|
||||
@ApiResponse(responseCode = "404", description = "Session with the specified ID could not be found")
|
||||
@ApiResponse(responseCode = "422", description = "Malformed session ID encoding")
|
||||
public VerificationSessionResponse getSession(@PathParam("sessionId") final String encodedSessionId) {
|
||||
|
||||
final RegistrationServiceSession registrationServiceSession = retrieveRegistrationServiceSession(encodedSessionId);
|
||||
|
@ -445,10 +490,45 @@ public class VerificationController {
|
|||
@Path("/session/{sessionId}/code")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "Request a verification code",
|
||||
description = """
|
||||
Sends a verification code to the phone number associated with the specified session via SMS or phone call.
|
||||
This endpoint can only be called when the session metadata includes "allowedToRequestCode = true"
|
||||
""")
|
||||
@ApiResponse(responseCode = "200", description = "Verification code was successfully sent", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "400", description = "Invalid session ID")
|
||||
@ApiResponse(responseCode = "404", description = "Session with the specified ID could not be found")
|
||||
@ApiResponse(responseCode = "409", description = "The session is already verified or not in a state to request a code because requested information hasn't been provided yet",
|
||||
content = @Content(schema = @Schema(implementation = VerificationSessionResponse.class)))
|
||||
@ApiResponse(responseCode = "418", description = "The request to send a verification code with the given transport could not be fulfilled, but may succeed with a different transport",
|
||||
content = @Content(schema = @Schema(implementation = VerificationSessionResponse.class)))
|
||||
@ApiResponse(responseCode = "422", description = "Request did not pass validation")
|
||||
@ApiResponse(responseCode = "429", description = """
|
||||
Too may attempts; the caller is not permitted to send a verification code via the requested channel at this time
|
||||
and may need to wait before trying again; if the session metadata does not specify a time at which the caller may
|
||||
try again, then the caller has exhausted their permitted attempts and must either try a different transport or
|
||||
create a new verification session.
|
||||
""",
|
||||
content = @Content(schema = @Schema(implementation = VerificationSessionResponse.class)),
|
||||
headers = @Header(
|
||||
name = "Retry-After",
|
||||
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed",
|
||||
schema = @Schema(implementation = Integer.class)
|
||||
))
|
||||
@ApiResponse(responseCode = "440", description = """
|
||||
The attempt to send a verification code failed because an external service (e.g. the SMS provider) refused to
|
||||
deliver the code. This may be a temporary or permanent failure, as indicated in the response body. If temporary,
|
||||
clients may try again after a reasonable delay. If permanent, clients should not retry the request and should
|
||||
communicate the permanent failure to the end user. Permanent failures may result in the server disallowing all
|
||||
future attempts to request or submit verification codes (since those attempts would be all but guaranteed to fail).
|
||||
""",
|
||||
content = @Content(schema = @Schema(implementation = RegistrationServiceSenderExceptionMapper.SendVerificationCodeFailureResponse.class)))
|
||||
public VerificationSessionResponse requestVerificationCode(@PathParam("sessionId") final String encodedSessionId,
|
||||
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent,
|
||||
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) Optional<String> acceptLanguage,
|
||||
@NotNull @Valid VerificationCodeRequest verificationCodeRequest) throws Throwable {
|
||||
@Parameter(in = ParameterIn.HEADER, description = "Ordered list of languages in which the client prefers to receive SMS or voice verification messages") @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE)
|
||||
final Optional<String> acceptLanguage,
|
||||
@NotNull @Valid final VerificationCodeRequest verificationCodeRequest) throws Throwable {
|
||||
|
||||
final RegistrationServiceSession registrationServiceSession = retrieveRegistrationServiceSession(encodedSessionId);
|
||||
final VerificationSession verificationSession = retrieveVerificationSession(registrationServiceSession);
|
||||
|
@ -550,8 +630,31 @@ public class VerificationController {
|
|||
@Path("/session/{sessionId}/code")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "Submit a verification code",
|
||||
description = """
|
||||
Submits a verification code received via SMS or voice for verification
|
||||
""")
|
||||
@ApiResponse(responseCode = "200", description = """
|
||||
The request to check a verification code was processed (though the submitted code may not be the correct code);
|
||||
the session metadata will indicate whether the submitted code was correct
|
||||
""", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "400", description = "Invalid session ID or verification code")
|
||||
@ApiResponse(responseCode = "404", description = "Session with the specified ID could not be found")
|
||||
@ApiResponse(responseCode = "409", description = "The session is already verified or no code has been requested yet for this session",
|
||||
content = @Content(schema = @Schema(implementation = VerificationSessionResponse.class)))
|
||||
@ApiResponse(responseCode = "429", description = """
|
||||
Too many attempts; the caller is not permitted to submit a verification code at this time and may need to wait
|
||||
before trying again; if the session metadata does not specify a time at which the caller may try again, then the
|
||||
caller has exhausted their permitted attempts and must create a new verification session.
|
||||
""",
|
||||
content = @Content(schema = @Schema(implementation = VerificationSessionResponse.class)),
|
||||
headers = @Header(
|
||||
name = "Retry-After",
|
||||
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed",
|
||||
schema = @Schema(implementation = Integer.class)))
|
||||
public VerificationSessionResponse verifyCode(@PathParam("sessionId") final String encodedSessionId,
|
||||
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent,
|
||||
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent,
|
||||
@NotNull @Valid final SubmitVerificationCodeRequest submitVerificationCodeRequest)
|
||||
throws RateLimitExceededException {
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.entities;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import org.whispersystems.textsecuregcm.util.E164;
|
||||
|
@ -16,6 +17,7 @@ import org.whispersystems.textsecuregcm.util.E164;
|
|||
// https://github.com/FasterXML/jackson-databind/issues/1497
|
||||
public final class CreateVerificationSessionRequest {
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "The e164-formatted phone number to be verified")
|
||||
@E164
|
||||
@NotBlank
|
||||
@JsonProperty
|
||||
|
|
|
@ -7,14 +7,26 @@ package org.whispersystems.textsecuregcm.entities;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.annotation.Nullable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.whispersystems.textsecuregcm.push.PushNotification;
|
||||
|
||||
public record UpdateVerificationSessionRequest(@Nullable String pushToken,
|
||||
@Nullable PushTokenType pushTokenType,
|
||||
@Nullable String pushChallenge,
|
||||
@Nullable String captcha,
|
||||
@Nullable String mcc,
|
||||
@Nullable String mnc) {
|
||||
public record UpdateVerificationSessionRequest(
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "The APNs or FCM device token to which a push challenge can be sent")
|
||||
@Nullable String pushToken,
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "The type of push token")
|
||||
@Nullable PushTokenType pushTokenType,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Value received by the device in the push challenge")
|
||||
@Nullable String pushChallenge,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Captcha token returned after solving a captcha challenge")
|
||||
@Nullable String captcha,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Mobile country code of the phone subscriber")
|
||||
@Nullable String mcc,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Mobile network code of the phone subscriber")
|
||||
@Nullable String mnc) {
|
||||
|
||||
public enum PushTokenType {
|
||||
@JsonProperty("apn")
|
||||
|
|
|
@ -6,10 +6,15 @@
|
|||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.whispersystems.textsecuregcm.registration.MessageTransport;
|
||||
|
||||
public record VerificationCodeRequest(@NotNull Transport transport, @NotNull String client) {
|
||||
public record VerificationCodeRequest(@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Transport via which to send the verification code")
|
||||
@NotNull Transport transport,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Client type to facilitate platform-specific SMS verification")
|
||||
@NotNull String client) {
|
||||
|
||||
public enum Transport {
|
||||
@JsonProperty("sms")
|
||||
|
|
|
@ -7,11 +7,29 @@ package org.whispersystems.textsecuregcm.entities;
|
|||
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.whispersystems.textsecuregcm.registration.VerificationSession;
|
||||
|
||||
public record VerificationSessionResponse(String id, @Nullable Long nextSms, @Nullable Long nextCall,
|
||||
@Nullable Long nextVerificationAttempt, boolean allowedToRequestCode,
|
||||
List<VerificationSession.Information> requestedInformation,
|
||||
boolean verified) {
|
||||
public record VerificationSessionResponse(
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "A URL-safe ID for the session")
|
||||
String id,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Duration in seconds after which next SMS can be requested for this session")
|
||||
@Nullable Long nextSms,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Duration in seconds after which next voice call can be requested for this session")
|
||||
@Nullable Long nextCall,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Duration in seconds after which the client can submit a verification code for this session")
|
||||
@Nullable Long nextVerificationAttempt,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Whether it is allowed to request a verification code for this session")
|
||||
boolean allowedToRequestCode,
|
||||
|
||||
@Schema(description = "A list of requested information that the client needs to submit before requesting code delivery")
|
||||
List<VerificationSession.Information> requestedInformation,
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Whether this session is verified")
|
||||
boolean verified) {
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue