-
-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Instagram authentication (#372)
* feat: add Instagram login * fix: swiftlint warnings. * test: add test for using default api url for Instagram login * docs: fix documentation for id parameter * Update CHANGELOG.md * fix: swiftlint warnings. * test: fix test implementations
- Loading branch information
Showing
8 changed files
with
1,604 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
93 changes: 93 additions & 0 deletions
93
Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+async.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// | ||
// ParseInstagram+async.swift | ||
// ParseSwift | ||
// | ||
// Created by Ulaş Sancak on 06/19/22. | ||
// Copyright © 2022 Parse Community. All rights reserved. | ||
// | ||
|
||
#if compiler(>=5.5.2) && canImport(_Concurrency) | ||
import Foundation | ||
|
||
public extension ParseInstagram { | ||
// MARK: Async/Await | ||
|
||
/** | ||
Login a `ParseUser` *asynchronously* using Instagram authentication. | ||
- parameter id: The **Instagram profile id** from **Instagram**. | ||
- parameter accessToken: Required **access_token** from **Instagram**. | ||
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. | ||
- parameter options: A set of header options sent to the server. Defaults to an empty set. | ||
- returns: An instance of the logged in `ParseUser`. | ||
- throws: An error of type `ParseError`. | ||
*/ | ||
func login(id: String, | ||
accessToken: String, | ||
apiURL: String = Self.graphAPIBaseURL, | ||
options: API.Options = []) async throws -> AuthenticatedUser { | ||
try await withCheckedThrowingContinuation { continuation in | ||
self.login(id: id, | ||
accessToken: accessToken, | ||
apiURL: apiURL, | ||
options: options, | ||
completion: continuation.resume) | ||
} | ||
} | ||
|
||
/** | ||
Login a `ParseUser` *asynchronously* using Instagram authentication. | ||
- parameter authData: Dictionary containing key/values. | ||
- returns: An instance of the logged in `ParseUser`. | ||
- throws: An error of type `ParseError`. | ||
*/ | ||
func login(authData: [String: String], | ||
options: API.Options = []) async throws -> AuthenticatedUser { | ||
try await withCheckedThrowingContinuation { continuation in | ||
self.login(authData: authData, | ||
options: options, | ||
completion: continuation.resume) | ||
} | ||
} | ||
} | ||
|
||
public extension ParseInstagram { | ||
|
||
/** | ||
Link the *current* `ParseUser` *asynchronously* using Instagram authentication. | ||
- parameter id: The **Instagram profile id** from **Instagram**. | ||
- parameter accessToken: Required **access_token** from **Instagram**. | ||
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. | ||
- parameter options: A set of header options sent to the server. Defaults to an empty set. | ||
- returns: An instance of the logged in `ParseUser`. | ||
- throws: An error of type `ParseError`. | ||
*/ | ||
func link(id: String, | ||
accessToken: String, | ||
apiURL: String = Self.graphAPIBaseURL, | ||
options: API.Options = []) async throws -> AuthenticatedUser { | ||
try await withCheckedThrowingContinuation { continuation in | ||
self.link(id: id, | ||
accessToken: accessToken, | ||
apiURL: apiURL, | ||
options: options, | ||
completion: continuation.resume) | ||
} | ||
} | ||
|
||
/** | ||
Link the *current* `ParseUser` *asynchronously* using Instagram authentication. | ||
- parameter authData: Dictionary containing key/values. | ||
- parameter options: A set of header options sent to the server. Defaults to an empty set. | ||
- returns: An instance of the logged in `ParseUser`. | ||
- throws: An error of type `ParseError`. | ||
*/ | ||
func link(authData: [String: String], | ||
options: API.Options = []) async throws -> AuthenticatedUser { | ||
try await withCheckedThrowingContinuation { continuation in | ||
self.link(authData: authData, | ||
options: options, | ||
completion: continuation.resume) | ||
} | ||
} | ||
} | ||
#endif |
90 changes: 90 additions & 0 deletions
90
Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+combine.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// | ||
// ParseInstagram+combine.swift | ||
// ParseSwift | ||
// | ||
// Created by Ulaş Sancak on 06/19/22. | ||
// Copyright © 2022 Parse Community. All rights reserved. | ||
// | ||
|
||
#if canImport(Combine) | ||
import Foundation | ||
import Combine | ||
|
||
public extension ParseInstagram { | ||
// MARK: Combine | ||
/** | ||
Login a `ParseUser` *asynchronously* using Instagram authentication. Publishes when complete. | ||
- parameter id: The **Instagram profile id** from **Instagram**. | ||
- parameter accessToken: Required **access_token** from **Instagram**. | ||
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. | ||
- parameter options: A set of header options sent to the server. Defaults to an empty set. | ||
- returns: A publisher that eventually produces a single value and then finishes or fails. | ||
*/ | ||
func loginPublisher(id: String, | ||
accessToken: String, | ||
apiURL: String = Self.graphAPIBaseURL, | ||
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> { | ||
Future { promise in | ||
self.login(id: id, | ||
accessToken: accessToken, | ||
apiURL: apiURL, | ||
options: options, | ||
completion: promise) | ||
} | ||
} | ||
|
||
/** | ||
Login a `ParseUser` *asynchronously* using Instagram authentication. Publishes when complete. | ||
- parameter authData: Dictionary containing key/values. | ||
- returns: A publisher that eventually produces a single value and then finishes or fails. | ||
*/ | ||
func loginPublisher(authData: [String: String], | ||
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> { | ||
Future { promise in | ||
self.login(authData: authData, | ||
options: options, | ||
completion: promise) | ||
} | ||
} | ||
} | ||
|
||
public extension ParseInstagram { | ||
/** | ||
Link the *current* `ParseUser` *asynchronously* using Instagram authentication. | ||
Publishes when complete. | ||
- parameter id: The **Instagram profile id** from **Instagram**. | ||
- parameter accessToken: Required **access_token** from **Instagram**. | ||
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. | ||
- parameter options: A set of header options sent to the server. Defaults to an empty set. | ||
- returns: A publisher that eventually produces a single value and then finishes or fails. | ||
*/ | ||
func linkPublisher(id: String, | ||
accessToken: String, | ||
apiURL: String = Self.graphAPIBaseURL, | ||
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> { | ||
Future { promise in | ||
self.link(id: id, | ||
accessToken: accessToken, | ||
apiURL: apiURL, | ||
options: options, | ||
completion: promise) | ||
} | ||
} | ||
|
||
/** | ||
Link the *current* `ParseUser` *asynchronously* using Instagram authentication. | ||
Publishes when complete. | ||
- parameter authData: Dictionary containing key/values. | ||
- returns: A publisher that eventually produces a single value and then finishes or fails. | ||
*/ | ||
func linkPublisher(authData: [String: String], | ||
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> { | ||
Future { promise in | ||
self.link(authData: authData, | ||
options: options, | ||
completion: promise) | ||
} | ||
} | ||
} | ||
|
||
#endif |
174 changes: 174 additions & 0 deletions
174
Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// | ||
// ParseInstagram.swift | ||
// ParseSwift | ||
// | ||
// Created by Ulaş Sancak on 06/19/22. | ||
// Copyright © 2022 Parse Community. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
// swiftlint:disable line_length | ||
|
||
/** | ||
Provides utility functions for working with Instagram User Authentication and `ParseUser`'s. | ||
Be sure your Parse Server is configured for [sign in with Instagram](https://docs.parseplatform.org/parse-server/guide/#instagram-authdata). | ||
For information on acquiring Instagram sign-in credentials to use with `ParseInstagram`, refer to [Facebook's Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/overview). | ||
*/ | ||
public struct ParseInstagram<AuthenticatedUser: ParseUser>: ParseAuthentication { | ||
|
||
public static var graphAPIBaseURL: String { | ||
"https://graph.instagram.com/" | ||
} | ||
|
||
/// Authentication keys required for Instagram authentication. | ||
enum AuthenticationKeys: String, Codable { | ||
case id | ||
case accessToken = "access_token" | ||
case apiURL | ||
|
||
/// Properly makes an authData dictionary with the required keys. | ||
/// - parameter id: Required id for the user. | ||
/// - parameter accessToken: Required access token for Instagram. | ||
/// - returns: authData dictionary. | ||
func makeDictionary(id: String, | ||
accessToken: String, | ||
apiURL: String = ParseInstagram.graphAPIBaseURL) -> [String: String] { | ||
|
||
let returnDictionary = [ | ||
AuthenticationKeys.id.rawValue: id, | ||
AuthenticationKeys.accessToken.rawValue: accessToken, | ||
AuthenticationKeys.apiURL.rawValue: apiURL | ||
] | ||
return returnDictionary | ||
} | ||
|
||
/// Verifies all mandatory keys are in authData. | ||
/// - parameter authData: Dictionary containing key/values. | ||
/// - returns: **true** if all the mandatory keys are present, **false** otherwise. | ||
func verifyMandatoryKeys(authData: [String: String]) -> Bool { | ||
guard authData[AuthenticationKeys.id.rawValue] != nil, | ||
authData[AuthenticationKeys.accessToken.rawValue] != nil, | ||
authData[AuthenticationKeys.apiURL.rawValue] != nil else { | ||
return false | ||
} | ||
return true | ||
} | ||
} | ||
|
||
public static var __type: String { // swiftlint:disable:this identifier_name | ||
"instagram" | ||
} | ||
|
||
public init() { } | ||
} | ||
|
||
// MARK: Login | ||
public extension ParseInstagram { | ||
|
||
/** | ||
Login a `ParseUser` *asynchronously* using Instagram authentication. | ||
- parameter id: The **Instagram profile id** from **Instagram**. | ||
- parameter accessToken: Required **access_token** from **Instagram**. | ||
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. | ||
- parameter options: A set of header options sent to the server. Defaults to an empty set. | ||
- parameter callbackQueue: The queue to return to after completion. Default value of .main. | ||
- parameter completion: The block to execute. | ||
*/ | ||
func login(id: String, | ||
accessToken: String, | ||
apiURL: String = Self.graphAPIBaseURL, | ||
options: API.Options = [], | ||
callbackQueue: DispatchQueue = .main, | ||
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) { | ||
|
||
let instagramAuthData = AuthenticationKeys.id | ||
.makeDictionary(id: id, | ||
accessToken: accessToken, | ||
apiURL: apiURL) | ||
print(instagramAuthData) | ||
login(authData: instagramAuthData, | ||
options: options, | ||
callbackQueue: callbackQueue, | ||
completion: completion) | ||
} | ||
|
||
func login(authData: [String: String], | ||
options: API.Options = [], | ||
callbackQueue: DispatchQueue = .main, | ||
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) { | ||
guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData) else { | ||
callbackQueue.async { | ||
completion(.failure(.init(code: .unknownError, | ||
message: "Should have authData in consisting of keys \"id\", \"accessToken\", and \"isMobileSDK\"."))) | ||
} | ||
return | ||
} | ||
AuthenticatedUser.login(Self.__type, | ||
authData: authData, | ||
options: options, | ||
callbackQueue: callbackQueue, | ||
completion: completion) | ||
} | ||
} | ||
|
||
// MARK: Link | ||
public extension ParseInstagram { | ||
|
||
/** | ||
Link the *current* `ParseUser` *asynchronously* using Instagram authentication. | ||
- parameter id: The **Instagram profile id** from **Instagram**. | ||
- parameter accessToken: Required **access_token** from **Instagram**. | ||
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. | ||
- parameter options: A set of header options sent to the server. Defaults to an empty set. | ||
- parameter callbackQueue: The queue to return to after completion. Default value of .main. | ||
- parameter completion: The block to execute. | ||
*/ | ||
func link(id: String, | ||
accessToken: String, | ||
apiURL: String = Self.graphAPIBaseURL, | ||
options: API.Options = [], | ||
callbackQueue: DispatchQueue = .main, | ||
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) { | ||
let instagramAuthData = AuthenticationKeys.id | ||
.makeDictionary(id: id, | ||
accessToken: accessToken, | ||
apiURL: apiURL) | ||
link(authData: instagramAuthData, | ||
options: options, | ||
callbackQueue: callbackQueue, | ||
completion: completion) | ||
} | ||
|
||
func link(authData: [String: String], | ||
options: API.Options = [], | ||
callbackQueue: DispatchQueue = .main, | ||
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) { | ||
guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData) else { | ||
callbackQueue.async { | ||
completion(.failure(.init(code: .unknownError, | ||
message: "Should have authData in consisting of keys \"id\", \"accessToken\", and \"isMobileSDK\"."))) | ||
} | ||
return | ||
} | ||
AuthenticatedUser.link(Self.__type, | ||
authData: authData, | ||
options: options, | ||
callbackQueue: callbackQueue, | ||
completion: completion) | ||
} | ||
} | ||
|
||
// MARK: 3rd Party Authentication - ParseInstagram | ||
public extension ParseUser { | ||
|
||
/// A Instagram `ParseUser`. | ||
static var instagram: ParseInstagram<Self> { | ||
ParseInstagram<Self>() | ||
} | ||
|
||
/// An Instagram `ParseUser`. | ||
var instagram: ParseInstagram<Self> { | ||
Self.instagram | ||
} | ||
} |
Oops, something went wrong.