diff --git a/FirebaseAuth/Sources/Auth/FIRAuth.m b/FirebaseAuth/Sources/Auth/FIRAuth.m index 7762806e2d..64998e1888 100644 --- a/FirebaseAuth/Sources/Auth/FIRAuth.m +++ b/FirebaseAuth/Sources/Auth/FIRAuth.m @@ -82,195 +82,12 @@ static NSString *const kInvalidEmailSignInLinkExceptionMessage = @"The link provided is not valid for email/link sign-in. Please check the link by calling " "isSignInWithEmailLink:link: on Auth before attempting to use it for email/link sign-in."; -/** @var kPasswordResetRequestType - @brief The action code type value for resetting password in the check action code response. - */ -static NSString *const kPasswordResetRequestType = @"PASSWORD_RESET"; - -/** @var kVerifyEmailRequestType - @brief The action code type value for verifying email in the check action code response. - */ -static NSString *const kVerifyEmailRequestType = @"VERIFY_EMAIL"; - -/** @var kRecoverEmailRequestType - @brief The action code type value for recovering email in the check action code response. - */ -static NSString *const kRecoverEmailRequestType = @"RECOVER_EMAIL"; - -/** @var kEmailLinkSignInRequestType - @brief The action code type value for an email sign-in link in the check action code response. -*/ -static NSString *const kEmailLinkSignInRequestType = @"EMAIL_SIGNIN"; - -/** @var kVerifyAndChangeEmailRequestType - @brief The action code type value for verifying and changing email in the check action code - response. - */ -static NSString *const kVerifyAndChangeEmailRequestType = @"VERIFY_AND_CHANGE_EMAIL"; - -/** @var kRevertSecondFactorAdditionRequestType - @brief The action code type value for reverting second factor addition in the check action code - response. - */ -static NSString *const kRevertSecondFactorAdditionRequestType = @"REVERT_SECOND_FACTOR_ADDITION"; - /** @var kMissingPasswordReason @brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown. @remarks This error message will be localized in the future. */ static NSString *const kMissingPasswordReason = @"Missing Password"; -#pragma mark - FIRActionCodeInfo - -@interface FIRActionCodeInfo () - -/** - @brief The operation being performed. - */ -@property(nonatomic, readwrite) FIRActionCodeOperation operation; - -/** @property email - @brief The email address to which the code was sent. The new email address in the case of - FIRActionCodeOperationRecoverEmail. - */ -@property(nonatomic, nullable, readwrite, copy) NSString *email; - -/** @property previousEmail - @brief The current email address in the case of FIRActionCodeOperationRecoverEmail. - */ -@property(nonatomic, nullable, readwrite, copy) NSString *previousEmail; - -#if TARGET_OS_IOS -/** @property multiFactorInfo - @brief The MultiFactorInfo object of the second factor to be reverted in case of - FIRActionCodeMultiFactorInfoKey. - */ -@property(nonatomic, nullable, readwrite) FIRMultiFactorInfo *multiFactorInfo; -#endif - -@end - -@implementation FIRActionCodeInfo - -- (instancetype)initWithOperation:(FIRActionCodeOperation)operation - email:(NSString *)email - newEmail:(nullable NSString *)newEmail { - self = [super init]; - if (self) { - _operation = operation; - if (newEmail) { - _email = [newEmail copy]; - _previousEmail = [email copy]; - } else { - _email = [email copy]; - } - } - return self; -} - -/** @fn actionCodeOperationForRequestType: - @brief Returns the corresponding operation type per provided request type string. - @param requestType Request type returned in in the server response. - @return The corresponding FIRActionCodeOperation for the supplied request type. - */ -+ (FIRActionCodeOperation)actionCodeOperationForRequestType:(NSString *)requestType { - if ([requestType isEqualToString:kPasswordResetRequestType]) { - return FIRActionCodeOperationPasswordReset; - } - if ([requestType isEqualToString:kVerifyEmailRequestType]) { - return FIRActionCodeOperationVerifyEmail; - } - if ([requestType isEqualToString:kRecoverEmailRequestType]) { - return FIRActionCodeOperationRecoverEmail; - } - if ([requestType isEqualToString:kEmailLinkSignInRequestType]) { - return FIRActionCodeOperationEmailLink; - } - if ([requestType isEqualToString:kVerifyAndChangeEmailRequestType]) { - return FIRActionCodeOperationVerifyAndChangeEmail; - } - if ([requestType isEqualToString:kRevertSecondFactorAdditionRequestType]) { - return FIRActionCodeOperationRevertSecondFactorAddition; - } - return FIRActionCodeOperationUnknown; -} - -@end - -#pragma mark - FIRActionCodeURL - -@implementation FIRActionCodeURL - -/** @fn FIRAuthParseURL:NSString - @brief Parses an incoming URL into all available query items. - @param urlString The url to be parsed. - @return A dictionary of available query items in the target URL. - */ -+ (NSDictionary *)parseURL:(NSString *)urlString { - NSString *linkURL = [NSURLComponents componentsWithString:urlString].query; - if (!linkURL) { - return @{}; - } - NSArray *URLComponents = [linkURL componentsSeparatedByString:@"&"]; - NSMutableDictionary *queryItems = - [[NSMutableDictionary alloc] initWithCapacity:URLComponents.count]; - for (NSString *component in URLComponents) { - NSRange equalRange = [component rangeOfString:@"="]; - if (equalRange.location != NSNotFound) { - NSString *queryItemKey = - [[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding]; - NSString *queryItemValue = - [[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding]; - if (queryItemKey && queryItemValue) { - queryItems[queryItemKey] = queryItemValue; - } - } - } - return queryItems; -} - -+ (nullable instancetype)actionCodeURLWithLink:(NSString *)link { - NSDictionary *queryItems = [FIRActionCodeURL parseURL:link]; - if (!queryItems.count) { - NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link]; - queryItems = [FIRActionCodeURL parseURL:urlComponents.query]; - } - if (!queryItems.count) { - return nil; - } - NSString *APIKey = queryItems[@"apiKey"]; - NSString *actionCode = queryItems[@"oobCode"]; - NSString *continueURLString = queryItems[@"continueUrl"]; - NSString *languageCode = queryItems[@"languageCode"]; - NSString *mode = queryItems[@"mode"]; - NSString *tenantID = queryItems[@"tenantID"]; - return [[FIRActionCodeURL alloc] initWithAPIKey:APIKey - actionCode:actionCode - continueURLString:continueURLString - languageCode:languageCode - mode:mode - tenantID:tenantID]; -} - -- (nullable instancetype)initWithAPIKey:(NSString *)APIKey - actionCode:(NSString *)actionCode - continueURLString:(NSString *)continueURLString - languageCode:(NSString *)languageCode - mode:(NSString *)mode - tenantID:(NSString *)tenantID { - self = [super init]; - if (self) { - _APIKey = APIKey; - _operation = [FIRActionCodeInfo actionCodeOperationForRequestType:mode]; - _code = actionCode; - _continueURL = [NSURL URLWithString:continueURLString]; - _languageCode = languageCode; - } - return self; -} - -@end - #pragma mark - FIRAuth #if TARGET_OS_IOS diff --git a/FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuth.h b/FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuth.h index a1d9b79aef..14629add06 100644 --- a/FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuth.h +++ b/FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuth.h @@ -36,6 +36,8 @@ @protocol FIRAuthStorage; @class FIRAuthAPNSTokenManager; @class FIRAuthAppCredentialManager; +@class FIRActionCodeInfo; +@class FIRActionCodeURL; NS_ASSUME_NONNULL_BEGIN @@ -176,109 +178,6 @@ typedef void (^FIRApplyActionCodeCallback)(NSError *_Nullable error) typedef void (^FIRAuthVoidErrorCallback)(NSError *_Nullable) NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead."); -/** @class FIRActionCodeInfo - @brief Manages information regarding action codes. - */ -NS_SWIFT_NAME(ActionCodeInfo) -@interface FIRActionCodeInfo : NSObject - -/** - @brief Operations which can be performed with action codes. - */ -typedef NS_ENUM(NSInteger, FIRActionCodeOperation) { - /** Action code for unknown operation. */ - FIRActionCodeOperationUnknown = 0, - - /** Action code for password reset operation. */ - FIRActionCodeOperationPasswordReset = 1, - - /** Action code for verify email operation. */ - FIRActionCodeOperationVerifyEmail = 2, - - /** Action code for recover email operation. */ - FIRActionCodeOperationRecoverEmail = 3, - - /** Action code for email link operation. */ - FIRActionCodeOperationEmailLink = 4, - - /** Action code for verifying and changing email */ - FIRActionCodeOperationVerifyAndChangeEmail = 5, - - /** Action code for reverting second factor addition */ - FIRActionCodeOperationRevertSecondFactorAddition = 6, - -} NS_SWIFT_NAME(ActionCodeOperation); - -/** - @brief The operation being performed. - */ -@property(nonatomic, readonly) FIRActionCodeOperation operation; - -/** @property email - @brief The email address to which the code was sent. The new email address in the case of - `ActionCodeOperationRecoverEmail`. - */ -@property(nonatomic, nullable, readonly, copy) NSString *email; - -/** @property previousEmail - @brief The email that is being recovered in the case of `ActionCodeOperationRecoverEmail`. - */ -@property(nonatomic, nullable, readonly, copy) NSString *previousEmail; - -/** @fn init - @brief please use initWithOperation: instead. - */ -- (instancetype)init NS_UNAVAILABLE; - -@end - -/** @class FIRActionCodeURL - @brief This class will allow developers to easily extract information about out of band links. - */ -NS_SWIFT_NAME(ActionCodeURL) -@interface FIRActionCodeURL : NSObject - -/** @property APIKey - @brief Returns the API key from the link. nil, if not provided. - */ -@property(nonatomic, nullable, copy, readonly) NSString *APIKey; - -/** @property operation - @brief Returns the mode of oob action. The property will be of `FIRActionCodeOperation` type. - It will return `FIRActionCodeOperationUnknown` if no oob action is provided. - */ -@property(nonatomic, readonly) FIRActionCodeOperation operation; - -/** @property code - @brief Returns the email action code from the link. nil, if not provided. - */ -@property(nonatomic, nullable, copy, readonly) NSString *code; - -/** @property continueURL - @brief Returns the continue URL from the link. nil, if not provided. - */ -@property(nonatomic, nullable, copy, readonly) NSURL *continueURL; - -/** @property languageCode - @brief Returns the language code from the link. nil, if not provided. - */ -@property(nonatomic, nullable, copy, readonly) NSString *languageCode; - -/** @fn actionCodeURLWithLink: - @brief Construct an `ActionCodeURL` from an out of band link (e.g. email link). - @param link The oob link string used to construct the action code URL. - @return The `ActionCodeURL` object constructed based on the oob link provided. - */ -+ (nullable instancetype)actionCodeURLWithLink:(NSString *)link; - -/** @fn init - @brief Please use `init(link:)` in Swift or `actionCodeURLWithLink:` in Objective-C - instead. - */ -- (instancetype)init NS_UNAVAILABLE; - -@end - /** @typedef FIRCheckActionCodeCallBack @brief The type of block invoked when performing a check action code operation. diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift new file mode 100644 index 0000000000..29d0c089b3 --- /dev/null +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift @@ -0,0 +1,68 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/** @class ActionCodeInfo + @brief Manages information regarding action codes. + */ +@objc(FIRActionCodeInfo) public class ActionCodeInfo: NSObject { + /** + @brief The operation being performed. + */ + @objc public let operation: ActionCodeOperation + + /** @property email + @brief The email address to which the code was sent. The new email address in the case of + `ActionCodeOperationRecoverEmail`. + */ + @objc public let email: String + + /** @property previousEmail + @brief The email that is being recovered in the case of `ActionCodeOperationRecoverEmail`. + */ + @objc public let previousEmail: String? + + // TODO: Below here change to internal. + + @objc public init(withOperation operation: ActionCodeOperation, email: String, + newEmail: String?) { + self.operation = operation + if let newEmail { + self.email = newEmail + previousEmail = email + } else { + self.email = email + previousEmail = nil + } + } + + /** @fn actionCodeOperationForRequestType: + @brief Returns the corresponding operation type per provided request type string. + @param requestType Request type returned in in the server response. + @return The corresponding ActionCodeOperation for the supplied request type. + */ + @objc public + class func actionCodeOperation(forRequestType requestType: String?) -> ActionCodeOperation { + switch requestType { + case "PASSWORD_RESET": return .passwordReset + case "VERIFY_EMAIL": return .verifyEmail + case "RECOVER_EMAIL": return .recoverEmail + case "EMAIL_SIGNIN": return .emailLink + case "VERIFY_AND_CHANGE_EMAIL": return .verifyAndChangeEmail + case "REVERT_SECOND_FACTOR_ADDITION": return .revertSecondFactorAddition + default: return .unknown + } + } +} diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift new file mode 100644 index 0000000000..24f5b41f05 --- /dev/null +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift @@ -0,0 +1,42 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/** + @brief Operations which can be performed with action codes. + */ +@objc(FIRActionCodeOperation) public enum ActionCodeOperation : Int, @unchecked Sendable { + + /** Action code for unknown operation. */ + case unknown = 0 + + /** Action code for password reset operation. */ + case passwordReset = 1 + + /** Action code for verify email operation. */ + case verifyEmail = 2 + + /** Action code for recover email operation. */ + case recoverEmail = 3 + + /** Action code for email link operation. */ + case emailLink = 4 + + /** Action code for verifying and changing email */ + case verifyAndChangeEmail = 5 + + /** Action code for reverting second factor addition */ + case revertSecondFactorAddition = 6 +} diff --git a/FirebaseAuth/Sources/Swift/Auth/ActionCodeSettings.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift similarity index 100% rename from FirebaseAuth/Sources/Swift/Auth/ActionCodeSettings.swift rename to FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift new file mode 100644 index 0000000000..86c1031049 --- /dev/null +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift @@ -0,0 +1,91 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/** @class FIRActionCodeURL + @brief This class will allow developers to easily extract information about out of band links. + */ +@objc(FIRActionCodeURL) public class ActionCodeURL: NSObject { + /** @property APIKey + @brief Returns the API key from the link. nil, if not provided. + */ + @objc public let apiKey: String? + + /** @property operation + @brief Returns the mode of oob action. The property will be of `FIRActionCodeOperation` type. + It will return `FIRActionCodeOperationUnknown` if no oob action is provided. + */ + @objc public let operation: ActionCodeOperation + + /** @property code + @brief Returns the email action code from the link. nil, if not provided. + */ + @objc public let code: String? + + /** @property continueURL + @brief Returns the continue URL from the link. nil, if not provided. + */ + @objc public let continueURL: URL? + + /** @property languageCode + @brief Returns the language code from the link. nil, if not provided. + */ + @objc public let languageCode: String? + + /** @fn actionCodeURLWithLink: + @brief Construct an `ActionCodeURL` from an out of band link (e.g. email link). + @param link The oob link string used to construct the action code URL. + @return The `ActionCodeURL` object constructed based on the oob link provided. + */ + @objc(actionCodeURLWithLink:) public init?(link: String) { + var queryItems = ActionCodeURL.parseURL(link) + if queryItems.count == 0 { + let urlComponents = URLComponents(string: link) + if let query = urlComponents?.query { + queryItems = ActionCodeURL.parseURL(query) + } + } + guard queryItems.count > 0 else { + return nil + } + apiKey = queryItems["apiKey"] + operation = ActionCodeInfo.actionCodeOperation(forRequestType: queryItems["mode"]) + code = queryItems["oobCode"] + if let continueURL = queryItems["continueUrl"] { + self.continueURL = URL(string: continueURL) + } else { + continueURL = nil + } + languageCode = queryItems["languageCode"] + } + + // TODO: internal below here. + + @objc public class func parseURL(_ urlString: String) -> [String: String] { + guard let linkURL = URLComponents(string: urlString)?.query else { + return [:] + } + var queryItems: [String: String] = [:] + let urlComponents = linkURL.components(separatedBy: "&") + for component in urlComponents { + let splitArray = component.split(separator: "=") + if let queryItemKey = String(splitArray[0]).removingPercentEncoding, + let queryItemValue = String(splitArray[1]).removingPercentEncoding { + queryItems[queryItemKey] = queryItemValue + } + } + return queryItems + } +}