355 lines
16 KiB
Swift
355 lines
16 KiB
Swift
//
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
public import LibSignalClient
|
|
|
|
public enum RegistrationServiceResponses {
|
|
|
|
// MARK: - Registration Session Endpoints
|
|
|
|
public enum BeginSessionResponseCodes: Int, UnknownEnumCodable {
|
|
/// Success. Response body has `RegistrationSession` object.
|
|
case success = 200
|
|
case missingArgument = 400
|
|
case invalidArgument = 422
|
|
/// The caller is not permitted to create a verification session and must wait before trying again.
|
|
/// Response will include a 'retry-after' header.
|
|
case retry = 429
|
|
case unexpectedError = -1
|
|
|
|
static public var unknown: Self { .unexpectedError }
|
|
}
|
|
|
|
public enum FetchSessionResponseCodes: Int, UnknownEnumCodable {
|
|
/// Success. Response body has `RegistrationSession` object.
|
|
case success = 200
|
|
/// No session was found with the given ID. A new session should be initiated.
|
|
case missingSession = 404
|
|
case unexpectedError = -1
|
|
|
|
static public var unknown: Self { .unexpectedError }
|
|
}
|
|
|
|
public enum FulfillChallengeResponseCodes: Int, UnknownEnumCodable {
|
|
/// Success. Response body has `RegistrationSession` object.
|
|
case success = 200
|
|
/// E.g. the challenge token provided did not match.
|
|
/// Response body has `RegistrationSession` object.
|
|
case notAccepted = 403
|
|
/// No session was found with the given ID. A new session should be initiated.
|
|
case missingSession = 404
|
|
case malformedRequest = 422
|
|
case unexpectedError = -1
|
|
|
|
static public var unknown: Self { .unexpectedError }
|
|
}
|
|
|
|
public enum RequestVerificationCodeResponseCodes: Int, UnknownEnumCodable {
|
|
/// Success. Response body has `RegistrationSession` object.
|
|
case success = 200
|
|
case malformedRequest = 400
|
|
/// No session was found with the given ID. A new session should be initiated.
|
|
case missingSession = 404
|
|
/// The current session state disallows requesting a code.
|
|
/// The client may have to fulfill some challenge before proceeding,
|
|
/// or the session might already be verified. Check the session object to know.
|
|
/// Response body has `RegistrationSession` object.
|
|
case disallowed = 409
|
|
/// The chosen transport mode is not supported (likely scoped to the e164),
|
|
/// but another transport may be supported.
|
|
case transportError = 418
|
|
/// May need to wait before trying again; check session object for timeouts.
|
|
/// If no timeout is specified, a different transport or starting a fresh session may be required.
|
|
/// Response body has `RegistrationSession` object.
|
|
case retry = 429
|
|
/// The attempt to send a verification code failed because an external service (e.g. the SMS provider) refused to deliver the code.
|
|
/// Response body has `SendVerificationCodeFailedResponse` with more detailed information.
|
|
case providerFailure = 440
|
|
case unexpectedError = -1
|
|
|
|
static public var unknown: Self { .unexpectedError }
|
|
}
|
|
|
|
public enum SubmitVerificationCodeResponseCodes: Int, UnknownEnumCodable {
|
|
/// The code was valid, but may not be correct. The
|
|
/// `isVerified` field on the session object indicates
|
|
/// correctness.
|
|
/// Response body has `RegistrationSession` object.
|
|
case success = 200
|
|
/// The code was illegally formatted.
|
|
case malformedRequest = 400
|
|
/// No session was found with the given ID. A new session should be initiated.
|
|
case missingSession = 404
|
|
/// This session will not accept additional verification code submissions either because no code has been sent for this session
|
|
/// (clients must request and presumably receive a verification code before submitting a code)
|
|
/// or because the phone number has already been verified with another code.
|
|
/// Response body has `RegistrationSession` object.
|
|
case newCodeRequired = 409
|
|
/// May need to wait before trying again; check session object for timeouts.
|
|
/// If no timeout is specified, sending a new code or starting a fresh session may be required.
|
|
/// Response body has `RegistrationSession` object.
|
|
case retry = 429
|
|
case unexpectedError = -1
|
|
|
|
static public var unknown: Self { .unexpectedError }
|
|
}
|
|
|
|
public struct RegistrationSession: Codable {
|
|
/// An opaque identifier for this session.
|
|
/// Clients will need to provide this ID to the API for subsequent operations.
|
|
/// The identifier will be made of URL-safe characters and will be less than 1024 bytes in length.
|
|
public let id: String
|
|
|
|
/// The time at which a client will next be able to request a verification SMS for this session.
|
|
/// If null, no further requests to send a verification SMS will be accepted.
|
|
/// Units are seconds from current time given in `X-Signal-Timestamp` header.
|
|
public let nextSms: Int?
|
|
|
|
/// The time at which a client will next be able to request a verification phone call for this session.
|
|
/// If null, no further requests to make a verification phone call will be accepted.
|
|
/// Units are seconds from current time given in `X-Signal-Timestamp` header.
|
|
public let nextCall: Int?
|
|
|
|
/// The time at which a client will next be able to submit a verification code for this session.
|
|
/// If null, no further attempts to submit a verification code will be accepted in the scope of this session.
|
|
/// Units are seconds from current time given in `X-Signal-Timestamp` header.
|
|
public let nextVerificationAttempt: Int?
|
|
|
|
/// Indicates whether clients are allowed to request verification code delivery via any transport mechanism.
|
|
/// If false, clients should provide the information listed in the `requestedInformation` list until
|
|
/// this field is `true` or the list of requested information contains no more options the client can fulfill.
|
|
/// If true, clients must still abide by the time limits set in `nextSms`, `nextCall`, and so on.
|
|
public let allowedToRequestCode: Bool
|
|
|
|
/// A list of additional information a client may be required to provide before requesting verification code delivery.
|
|
/// Additional requirements may appear in the future, and clients must be prepared to handle these cases gracefully
|
|
/// (e.g. by prompting users to update their copy of the app if unrecognized values appear in this list).
|
|
public let requestedInformation: [Challenge]
|
|
|
|
/// Indicates whether the caller has submitted a correct verification code for this session.
|
|
public let verified: Bool
|
|
|
|
public enum Challenge: String, UnknownEnumCodable {
|
|
case unknown
|
|
case captcha
|
|
case pushChallenge
|
|
}
|
|
}
|
|
|
|
public struct SendVerificationCodeFailedResponse: Codable {
|
|
/// Indicates whether the failure was permanent.
|
|
/// If true, clients should not retry the request without modification
|
|
/// (practically, this most likely means clients will need to ask users to re-enter their phone number).
|
|
/// If false, clients may retry the request after a reasonable delay.
|
|
public let permanentFailure: Bool
|
|
|
|
/// An identifier that indicates the cause of the failure.
|
|
/// This identifier is provided on a best-effort basis; it may or may not be present, and may include
|
|
/// values not recognized by the current version of the client.
|
|
/// Clients should be prepared to handle missing or unrecognized values.
|
|
public let reason: Reason?
|
|
|
|
public enum Reason: String, UnknownEnumCodable {
|
|
case unknown
|
|
/// The provider understood the request, but declined to deliver a verification SMS/call.
|
|
/// (potentially due to fraud prevention rules)
|
|
case providerRejected
|
|
/// The provider could not be reached or did not respond to the request to send a verification code in a timely manner
|
|
case providerUnavailable
|
|
/// Some part of the request was not understood or accepted by the provider.
|
|
/// (e.g. the provider did not recognize the phone number as a valid number for the selected transport)
|
|
case illegalArgument
|
|
}
|
|
}
|
|
|
|
// MARK: - SVR2 Auth Check
|
|
|
|
public enum SVR2AuthCheckResponseCodes: Int, UnknownEnumCodable {
|
|
/// Success. Response body has `SVR2AuthCheckResponse` object.
|
|
case success = 200
|
|
/// The server couldn't parse the set of credentials.
|
|
case malformedRequest = 422
|
|
/// The POST request body is not valid JSON.
|
|
case invalidJSON = 400
|
|
case unexpectedError = -1
|
|
|
|
static public var unknown: Self { .unexpectedError }
|
|
}
|
|
|
|
public struct SVR2AuthCheckResponse: Codable {
|
|
public let matches: [String: Result]
|
|
|
|
public enum Result: String, UnknownEnumCodable {
|
|
/// At most one credential will be marked as a `match` per request.
|
|
/// Clients should use this credential when re-registering the associated phone number.
|
|
case match
|
|
/// The provided credential is valid and should be retained by the client,
|
|
/// but cannot be used to re-register the provided number.
|
|
case notMatch = "not-match"
|
|
/// Indicates that the credential may not be used to re-register any phone number and should be discarded.
|
|
case invalid
|
|
|
|
// Server API explicitly says clients should treat unrecognized values as invalid.
|
|
static public var unknown: Self { return .invalid }
|
|
}
|
|
|
|
public func result(for credential: SVR2AuthCredential) -> Result? {
|
|
let key = "\(credential.credential.username):\(credential.credential.password)"
|
|
return matches[key]
|
|
}
|
|
}
|
|
|
|
// MARK: - Account Creation/Change Number
|
|
|
|
public enum AccountCreationResponseCodes: Int, UnknownEnumCodable {
|
|
/// Success. Response body has `AccountIdentityResponse`.
|
|
case success = 200
|
|
/// Incorrect request body shape, missing required Authorization header,
|
|
/// or Authorization e164 did not match e164 from session.
|
|
/// Response body has a string error message.
|
|
case malformedRequest = 400
|
|
/// The Authorization header was invalid or the provided credentials were insufficient
|
|
/// to verify ownership of the given phone number.
|
|
/// Response body has an optional string error message.
|
|
case unauthorized = 401
|
|
/// The provided registration recovery password is either incorrect
|
|
/// or registration via reg recovery password is impossible for this number.
|
|
case regRecoveryPasswordRejected = 403
|
|
/// The caller has not explicitly elected to skip transferring data
|
|
/// from another device, but a device transfer is technically possible.
|
|
case deviceTransferPossible = 409
|
|
/// Response body has an optional string error message.
|
|
///
|
|
/// NOTE: if `requireAtomic` is set on the request but other
|
|
/// atomic account creation fields are nil, the server will return 422.
|
|
case invalidArgument = 422
|
|
/// An account with the given phone number already exists and has a registration lock,
|
|
/// and the client has not provided appropriate reglock credentials (either because the
|
|
/// user inputted the wrong PIN, or because the client has the wrong random number
|
|
/// used to generate the master key).
|
|
/// Response body has `RegistrationLockFailureResponse`.
|
|
case reglockFailed = 423
|
|
/// The caller is not permitted to create an account and must wait before trying again.
|
|
/// Response will include a 'retry-after' header.
|
|
case retry = 429
|
|
case unexpectedError = -1
|
|
|
|
static public var unknown: Self { .unexpectedError }
|
|
}
|
|
|
|
public enum ChangeNumberResponseCodes: Int, UnknownEnumCodable {
|
|
/// Success. Response body has `AccountIdentityResponse`.
|
|
case success = 200
|
|
/// Incorrect request body shape, missing required Authorization header,
|
|
/// or Authorization e164 did not match e164 from session.
|
|
/// Response body has a string error message.
|
|
case malformedRequest = 400
|
|
/// The provided credentials were insufficient to verify ownership of the given phone number.
|
|
case unauthorized = 401
|
|
/// The provided registration recovery password is either incorrect
|
|
/// or registration via reg recovery password is impossible for this number.
|
|
case regRecoveryPasswordRejected = 403
|
|
/// The devices to notify in the request did not match the known
|
|
/// linked devices.
|
|
case mismatchedDevicesToNotify = 409
|
|
/// The devices to notify in the request were correct, but their
|
|
/// provided registrationIds did not match.
|
|
case mismatchedDevicesToNotifyRegistrationIds = 410
|
|
/// Response body has an optional string error message.
|
|
case invalidArgument = 422
|
|
/// An account with the given phone number already exists and has a registration lock,
|
|
/// and the client has not provided appropriate reglock credentials (either because the
|
|
/// user inputted the wrong PIN, or because the client has the wrong random number
|
|
/// used to generate the master key).
|
|
/// Response body has `RegistrationLockFailureResponse`.
|
|
case reglockFailed = 423
|
|
/// The caller is not permitted to change the number and must wait before trying again.
|
|
/// Response will include a 'retry-after' header.
|
|
case retry = 429
|
|
case unexpectedError = -1
|
|
|
|
static public var unknown: Self { .unexpectedError }
|
|
}
|
|
|
|
public struct AccountIdentityResponse: Codable, Equatable {
|
|
/// The users account identifier.
|
|
@AciUuid public var aci: Aci
|
|
/// The user's phone number identifier.
|
|
@PniUuid public var pni: Pni
|
|
/// The phone number associated with the PNI.
|
|
public let e164: E164
|
|
/// The username associated with the ACI.
|
|
public let username: String?
|
|
/// Whether the account has any data in SVR.
|
|
public let hasPreviouslyUsedSVR: Bool
|
|
|
|
public init(aci: Aci, pni: Pni, e164: E164, username: String?, hasPreviouslyUsedSVR: Bool) {
|
|
self._aci = aci.codableUuid
|
|
self._pni = pni.codableUuid
|
|
self.e164 = e164
|
|
self.username = username
|
|
self.hasPreviouslyUsedSVR = hasPreviouslyUsedSVR
|
|
}
|
|
|
|
public enum CodingKeys: String, CodingKey {
|
|
case aci = "uuid"
|
|
case pni
|
|
case e164 = "number"
|
|
case username
|
|
case hasPreviouslyUsedSVR = "storageCapable"
|
|
}
|
|
}
|
|
|
|
public struct RegistrationLockFailureResponse: Decodable {
|
|
/// Time remaining until the registration lock expires and the account
|
|
/// can be taken over.
|
|
public let timeRemainingMs: Int
|
|
/// A credential with which the client can talk to SVR2 server to
|
|
/// recover the SVR master key, and from it the reglock token,
|
|
/// using the user's PIN.
|
|
public let svr2AuthCredential: SVR2AuthCredential
|
|
|
|
public enum CodingKeys: String, CodingKey {
|
|
case timeRemainingMs = "timeRemaining"
|
|
case svr2AuthCredential = "svr2Credentials"
|
|
}
|
|
|
|
public init(
|
|
timeRemainingMs: Int,
|
|
svr2AuthCredential: SVR2AuthCredential
|
|
) {
|
|
self.timeRemainingMs = timeRemainingMs
|
|
self.svr2AuthCredential = svr2AuthCredential
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
timeRemainingMs = try container.decode(Int.self, forKey: .timeRemainingMs)
|
|
let svr2Credential = try container.decode(RemoteAttestation.Auth.self, forKey: .svr2AuthCredential)
|
|
self.svr2AuthCredential = .init(credential: svr2Credential)
|
|
}
|
|
}
|
|
|
|
// MARK: Check Proxy Connection
|
|
|
|
public enum CheckProxyConnectionResponseCodes: Int, UnknownEnumCodable {
|
|
case connected = 400
|
|
case failure = -1
|
|
|
|
static public var unknown: Self { .failure }
|
|
|
|
public init(rawValue: RawValue) {
|
|
switch rawValue {
|
|
case 200..<300:
|
|
self = .connected
|
|
case 400..<500:
|
|
self = .connected
|
|
default:
|
|
self = .failure
|
|
}
|
|
}
|
|
}
|
|
}
|