Skip to content

Commit

Permalink
feat: add Instagram authentication (#372)
Browse files Browse the repository at this point in the history
* 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
rocxteady authored Jun 20, 2022
1 parent 51a44ef commit 3b3b839
Show file tree
Hide file tree
Showing 8 changed files with 1,604 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
__New features__
- Add the ability to send APN and FCM push notifications. Also adds the ability to query _PushStatus ([#371](https://github.com/parse-community/Parse-Swift/pull/371)), thanks to [Corey Baker](https://github.com/cbaker6).
- Add ParseSchema, ParseCLP, and ParseFieldOptions. Should only be used when using the Swift SDK on a secured server ([#370](https://github.com/parse-community/Parse-Swift/pull/370)), thanks to [Corey Baker](https://github.com/cbaker6).
- Add ParseInstagram authentication ([#372](https://github.com/parse-community/Parse-Swift/pull/372)), thanks to [Ulaş Sancak](https://github.com/rocxteady).

### 4.5.0
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.4.0...4.5.0)
Expand Down
62 changes: 62 additions & 0 deletions ParseSwift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

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
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
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
}
}
Loading

0 comments on commit 3b3b839

Please sign in to comment.