Skip to content

Anonymous and Apple Login #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jan 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0099c63
Initial
cbaker6 Jan 15, 2021
0d7bd04
Update
cbaker6 Jan 15, 2021
4238438
Add ParseUser.become
cbaker6 Jan 15, 2021
67530c4
Added some playground examples. Bug fixes
cbaker6 Jan 16, 2021
73275de
Update docs
cbaker6 Jan 16, 2021
fa52a57
Fix Swift 5.2 closure issue
cbaker6 Jan 16, 2021
674d80b
Update ParseApple docs
cbaker6 Jan 16, 2021
ef10826
Update docs
cbaker6 Jan 16, 2021
c2a275d
update signupCommand with authData if none sent back from server.
cbaker6 Jan 16, 2021
1f308bd
Moved some methods to instance
cbaker6 Jan 16, 2021
10a4587
Clean up convenience methods
cbaker6 Jan 16, 2021
0e20651
Improve folder structure
cbaker6 Jan 16, 2021
df30d3d
Fixed unlinking/stripping of auths. Also removed reqs for implementin…
cbaker6 Jan 16, 2021
b1d05a7
Added support for potential Linux build
cbaker6 Jan 16, 2021
7e249ae
Add some tests
cbaker6 Jan 16, 2021
dfdefb7
Improvements
cbaker6 Jan 17, 2021
59baecb
Add testcases
cbaker6 Jan 17, 2021
85c9d16
Add Session protocol
cbaker6 Jan 17, 2021
0393f08
Remove session endpoints as they are not used for now.
cbaker6 Jan 17, 2021
88ea66a
Clean up
cbaker6 Jan 17, 2021
3986826
Fix unlink async callback
cbaker6 Jan 17, 2021
d05b67a
Apply suggestions from code review
cbaker6 Jan 18, 2021
b81f182
Make Session "restricted" key optional
cbaker6 Jan 18, 2021
fd2499b
try updated lock file to fix docs in CI
cbaker6 Jan 18, 2021
3208d97
See if jazzy stops giving issues with older tools
cbaker6 Jan 18, 2021
125ed0b
try older version while running jazzy
cbaker6 Jan 18, 2021
5e8ac47
See if Xcode 12.2 allows building of docs
cbaker6 Jan 18, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
branches: '*'
env:
CI_XCODE_VER: '/Applications/Xcode_11.7.app/Contents/Developer'
CI_XCODE_VER_12: '/Applications/Xcode_12.2.app/Contents/Developer'

jobs:
xcode-test-ios:
Expand Down Expand Up @@ -104,8 +105,12 @@ jobs:
run: |
bundle config path vendor/bundle
bundle install
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER_12 }}
- name: Create Jazzy Docs
run: ./Scripts/jazzy.sh
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER_12 }}
- name: Deploy Jazzy Docs
if: github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ GEM
escape (0.0.4)
ethon (0.12.0)
ffi (>= 1.3.0)
ffi (1.13.1)
ffi (1.14.2)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct User: ParseUser {
var username: String?
var email: String?
var password: String?
var authData: [String: [String: String]?]?

//: Your custom keys
var customKey: String?
Expand Down Expand Up @@ -76,6 +77,30 @@ do {
print("Error logging out: \(error)")
}

//: Logging in anonymously
User.anonymous.login { result in
switch result {
case .success:
print("Successfully logged in \(User.current)")
case .failure(let error):
print("Error logging in: \(error)")
}
}

//: Convert the anonymous user to a real new user.
User.current?.username = "bye"
User.current?.password = "world"
User.current?.signup { result in
switch result {

case .success(let user):
print("Parse signup successful: \(user)")

case .failure(let error):
print("Error logging in: \(error)")
}
}

//: Password Reset Request - synchronously
do {
try User.verificationEmailRequest(email: "[email protected]")
Expand All @@ -92,22 +117,6 @@ do {
print("Error requesting password reset: \(error)")
}

//: Another way to sign up
var newUser = User()
newUser.username = "hello10"
newUser.password = "world"

newUser.signup { result in
switch result {

case .success(let user):
print("Parse signup successful: \(user)")

case .failure(let error):
print("Error logging in: \(error)")
}
}

PlaygroundPage.current.finishExecution()

//: [Next](@next)
114 changes: 109 additions & 5 deletions ParseSwift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Sources/ParseSwift/API/Responses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ internal struct LoginSignupResponse: Codable {
let objectId: String
let sessionToken: String
var updatedAt: Date?
let username: String?
}

// MARK: ParseFile
Expand Down
150 changes: 150 additions & 0 deletions Sources/ParseSwift/Authentication/3rd Party/ParseApple.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//
// ParseApple.swift
// ParseSwift
//
// Created by Corey Baker on 1/14/21.
// Copyright © 2021 Parse Community. All rights reserved.
//

import Foundation

// swiftlint:disable line_length

/**
Provides utility functions for working with Apple User Authentication and `ParseUser`'s.
Be sure your Parse Server is configured for [sign in with Apple](https://docs.parseplatform.org/parse-server/guide/#configuring-parse-server-for-sign-in-with-apple).
For information on acquiring Apple sign-in credentials to use with `ParseApple`, refer to [Apple's Documentation](https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple).
*/
public struct ParseApple<AuthenticatedUser: ParseUser>: ParseAuthenticatable {

/// Authentication keys required for Apple authentication.
enum AuthenticationKeys: String, Codable {
case id // swiftlint:disable:this identifier_name
case token

/// Properly makes an authData dictionary with the required keys.
/// - parameter id: Required id.
/// - parameter token: Required token.
/// - returns: Required authData dictionary.
func makeDictionary(user: String,
identityToken: String) -> [String: String] {
[AuthenticationKeys.id.rawValue: user,
AuthenticationKeys.token.rawValue: identityToken]
}

/// 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 let authData = authData,
authData[AuthenticationKeys.id.rawValue] != nil,
authData[AuthenticationKeys.token.rawValue] != nil else {
return false
}
return true
}
}
public static var __type: String { // swiftlint:disable:this identifier_name
"apple"
}
public init() { }
}

// MARK: Login
public extension ParseApple {
/**
Login a `ParseUser` *asynchronously* using Apple authentication.
- parameter user: The `user` from `ASAuthorizationAppleIDCredential`.
- parameter identityToken: The `identityToken` from `ASAuthorizationAppleIDCredential`.
- 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(user: String,
identityToken: String,
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) {
login(authData: AuthenticationKeys.id.makeDictionary(user: user, identityToken: identityToken),
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),
let authData = authData else {
let error = ParseError(code: .unknownError,
message: "Should have authData in consisting of keys \"id\" and \"token\".")
callbackQueue.async {
completion(.failure(error))
}
return
}
AuthenticatedUser.login(Self.__type,
authData: authData,
options: options,
callbackQueue: callbackQueue,
completion: completion)
}
}

// MARK: Link
public extension ParseApple {

/**
Link the *current* `ParseUser` *asynchronously* using Apple authentication.
- parameter user: The `user` from `ASAuthorizationAppleIDCredential`.
- parameter identityToken: The `identityToken` from `ASAuthorizationAppleIDCredential`.
- 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(user: String,
identityToken: String,
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) {
link(authData: AuthenticationKeys.id.makeDictionary(user: user, identityToken: identityToken),
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),
let authData = authData else {
let error = ParseError(code: .unknownError,
message: "Should have authData in consisting of keys \"id\" and \"token\".")
callbackQueue.async {
completion(.failure(error))
}
return
}
AuthenticatedUser.link(Self.__type,
authData: authData,
options: options,
callbackQueue: callbackQueue,
completion: completion)
}
}

// MARK: 3rd Party Authentication - ParseApple
public extension ParseUser {

/// An apple `ParseUser`.
static var apple: ParseApple<Self> {
ParseApple<Self>()
}

/// An apple `ParseUser`.
var apple: ParseApple<Self> {
Self.apple
}
}
98 changes: 98 additions & 0 deletions Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// ParseAnonymous.swift
// ParseSwift
//
// Created by Corey Baker on 1/14/21.
// Copyright © 2021 Parse Community. All rights reserved.
//

import Foundation

/**
Provides utility functions for working with Anonymously logged-in users.

Anonymous users have some unique characteristics:
- Anonymous users don't need a user name or password.
- Once logged out, an anonymous user cannot be recovered.
- When the current user is anonymous, the following methods can be used to switch
to a different user or convert the anonymous user into a regular one:
- *signup* converts an anonymous user to a standard user with the given username and password.
Data associated with the anonymous user is retained.
- *login* switches users without converting the anonymous user.
Data associated with the anonymous user will be lost.
- Service *login* (e.g. Apple, Facebook, Twitter) will attempt to convert
the anonymous user into a standard user by linking it to the service.
If a user already exists that is linked to the service, it will instead switch to the existing user.
- Service linking (e.g. Apple, Facebook, Twitter) will convert the anonymous user
into a standard user by linking it to the service.
*/
public struct ParseAnonymous<AuthenticatedUser: ParseUser>: ParseAuthenticatable {

enum AuthenticationKeys: String, Codable {
case id // swiftlint:disable:this identifier_name

func makeDictionary() -> [String: String] {
[AuthenticationKeys.id.rawValue: UUID().uuidString.lowercased()]
}
}
public static var __type: String { // swiftlint:disable:this identifier_name
"anonymous"
}
public init() { }
}

// MARK: Login
public extension ParseAnonymous {
/**
Login a `ParseUser` *synchronously* using the respective authentication type.
- parameter authData: The authData for the respective authentication type.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- throws: `ParseError`.
- returns: the linked `ParseUser`.
*/
func login(authData: [String: String]? = nil,
options: API.Options = []) throws -> AuthenticatedUser {
return try AuthenticatedUser
.login(__type,
authData: AuthenticationKeys.id.makeDictionary(),
options: options)
}

func login(authData: [String: String]? = nil,
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) {
AuthenticatedUser.login(__type,
authData: AuthenticationKeys.id.makeDictionary(),
options: options,
callbackQueue: callbackQueue,
completion: completion)
}
}

// MARK: Link
public extension ParseAnonymous {
/// Unavailable for `ParseAnonymous`. Will always return an error.
func link(authData: [String: String]? = nil,
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) {
callbackQueue.async {
completion(.failure(ParseError(code: .unknownError, message: "Not supported")))
}
}
}

// MARK: ParseAnonymous
public extension ParseUser {

/// An anonymous `ParseUser`.
static var anonymous: ParseAnonymous<Self> {
ParseAnonymous<Self>()
}

/// An anonymous `ParseUser`.
var anonymous: ParseAnonymous<Self> {
Self.anonymous
}
}
Loading