// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation /// Client-only representation of a RegistrationSessionMetadata object returned by the server /// in registration endpoint responses. /// Represents state related to registration, answering questions like "can I request an SMS code?", /// "if so, when can I request one again?", and "have I submitted a valid verification code?". /// /// Intentionally distinct from the on-the-wire json definition; this is the one we store on disk /// and therefore want distinct for the purposes of migrations. public struct RegistrationSession: Codable, Equatable { /// Opaque id required by the server in requests. /// URL safe characters only, 1024 bytes max length. public let id: String /// The phone number (in e164 format) this session was created for. public let e164: E164 /// The date at which we received this session metadata from the server. /// All durations should be measured against this date. public let receivedDate: Date /// How long after `receivedDate` we must wait to send another verification sms. /// If null, no further SMS requests are allowed within this session. public let nextSMS: TimeInterval? public var nextSMSDate: Date? { return nextSMS.map { receivedDate.addingTimeInterval($0) } } /// How long after `receivedDate` we must wait to send another verification call. /// If null, no further call requests are allowed within this session. public let nextCall: TimeInterval? public var nextCallDate: Date? { return nextCall.map { receivedDate.addingTimeInterval($0) } } /// How long after `receivedDate` we must wait to submit a sms/call verification code to the server. /// If null, no code is available for submission, either because: /// 1) No code has ever been sent /// 2) A code was sent but it expired /// 3) All attempts have been exhausted for the current code /// In all of these cases, the next step is requesting a new code, which may change /// the value of this field. If a code cannot be requested, the session /// can be considered invalid and discarded. public let nextVerificationAttempt: TimeInterval? public var nextVerificationAttemptDate: Date? { return nextVerificationAttempt.map { receivedDate.addingTimeInterval($0) } } /// If true, the server believes that there is a code that has been sent, is still valid, /// and is waiting to be submitted (and attempts have not been exhausted). The user /// should be allowed to submit the code (or resend). /// If false, there is no code able to be submitted, a new code _must_ be requested, /// and the user should only be given code sending as an option. public var hasCodeAvailableToSubmit: Bool { return nextVerificationAttempt != nil } /// If true, `requestedInformation` can be ignored and the user can request a verification code /// be sent via sms or call (subject to time limits set by their respective fields.) /// If false, the demands in `requestedInformation` must be satisfied before a verification code /// will be sent. public let allowedToRequestCode: Bool public enum Challenge: Codable, Equatable { /// A captcha challenge to be shown and completed by the user. case captcha /// A silent push sent to the client, receipt of which proves AppStore installation validity. /// Requires no explicit user action. case pushChallenge } /// If `allowedToRequestCode` is true, the challenges herein should be completed in /// FIFO order as possible. e.g. if the client is incapable of completed the first challenge, /// it should attempt the second instead. /// It's possible not all challenges need be completed to proceed; clients should complete /// challenges one at a time and check the new session metadata in the response for /// additional challenge requirements, which may be none at all, before proceeding. public let requestedInformation: [Challenge] /// If true, it means some challenge was provided by the server that this client cannot /// interpret. Users should be warned to update the app before completing registration. public let hasUnknownChallengeRequiringAppUpdate: Bool /// If true, a correct verification code has been submitted in this session /// and registration can proceed to account creation. public let verified: Bool public enum CodingKeys: String, CodingKey { case id case e164 case receivedDate case nextSMS case nextCall case nextVerificationAttempt case allowedToRequestCode case requestedInformation case hasUnknownChallengeRequiringAppUpdate case verified } }