Skip to content

Add OAuth support #91

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

Closed
wants to merge 17 commits into from
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.2.5...main)
* _Contributing to this repo? Add info about your change here to be included in the next release_

__New features__
- Add OAuth 2.0 support ([#91](https://github.com/parse-community/Parse-Swift/pull/91)), thanks to [Corey Baker](https://github.com/cbaker6).

### 1.2.5
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.2.4...1.2.5)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ User.signup(username: "hello", password: "world") { results in
assertionFailure("Error: these two objects should match")
} else {
print("Successfully signed up user \(user)")
print("The users' sessionToken is: \(currentUser.sessionToken)")
if let accessToken = currentUser.accessToken {
print("The users' accessToken is: \(accessToken)")
print("The users' refreshToken is: \(currentUser.refreshToken!)")
print("The users' token expires at: \(currentUser.expiresAt!)")
}
}

case .failure(let error):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,31 @@ struct GameScore: ParseObject {
}
}

//: If signed in using OAuth2.0, ask the server to refresh the token
if let currentUser = User.current,
let accessToken = currentUser.accessToken {
print("The current sessionToken: \(currentUser.expiresAt)")
print("The current accessToken is: \(accessToken)")
print("The current refreshToken is: \(currentUser.refreshToken)")
print("The current token expires at: \(currentUser.expiresAt)")
User.refresh { results in

switch results {
case .success(let updatedUser):
print("Successfully refreshed users tokens")
if let updatedUser = User.current,
let accessToken = updatedUser.accessToken {
print("The new sessionToken: \(updatedUser.sessionToken)")
print("The new accessToken: \(updatedUser.accessToken)")
print("The new refreshToken is: \(updatedUser.refreshToken)")
print("The token expires at: \(updatedUser.expiresAt)")
}
case .failure(let error):
print("Failed to update user: \(error)")
}
}
}

//: Logging out - synchronously
do {
try User.logout()
Expand Down
8 changes: 8 additions & 0 deletions ParseSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@
707A3C2125B14BD0000D215C /* ParseApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707A3C1F25B14BCF000D215C /* ParseApple.swift */; };
707A3C2225B14BD0000D215C /* ParseApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707A3C1F25B14BCF000D215C /* ParseApple.swift */; };
707A3C2325B14BD0000D215C /* ParseApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707A3C1F25B14BCF000D215C /* ParseApple.swift */; };
707CCE9C25FBD4D0003C3B64 /* ParseUserOAuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707CCE9B25FBD4D0003C3B64 /* ParseUserOAuthTests.swift */; };
707CCE9D25FBD4D0003C3B64 /* ParseUserOAuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707CCE9B25FBD4D0003C3B64 /* ParseUserOAuthTests.swift */; };
707CCE9E25FBD4D0003C3B64 /* ParseUserOAuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707CCE9B25FBD4D0003C3B64 /* ParseUserOAuthTests.swift */; };
708D035225215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; };
708D035325215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; };
708D035425215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; };
Expand Down Expand Up @@ -606,6 +609,7 @@
707A3BF025B0A4F0000D215C /* ParseAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAuthentication.swift; sourceTree = "<group>"; };
707A3C1025B0A8E8000D215C /* ParseAnonymous.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnonymous.swift; sourceTree = "<group>"; };
707A3C1F25B14BCF000D215C /* ParseApple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseApple.swift; sourceTree = "<group>"; };
707CCE9B25FBD4D0003C3B64 /* ParseUserOAuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseUserOAuthTests.swift; sourceTree = "<group>"; };
708D035125215F9B00646C70 /* Deletable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deletable.swift; sourceTree = "<group>"; };
709B98302556EC7400507778 /* ParseSwiftTeststvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParseSwiftTeststvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
709B98342556EC7400507778 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -848,6 +852,7 @@
89899CDC2603CE73002E2043 /* ParseTwitterTests.swift */,
7016ED3F25C4A25A00038648 /* ParseUserCombineTests.swift */,
70C7DC1D24D20E530050419B /* ParseUserTests.swift */,
707CCE9B25FBD4D0003C3B64 /* ParseUserOAuthTests.swift */,
7FFF552A2217E729007C3B4E /* AnyCodableTests */,
911DB12A24C3F7260027F3C7 /* NetworkMocking */,
);
Expand Down Expand Up @@ -1702,6 +1707,7 @@
70A2D86B25B3ADB6001BEB7D /* ParseAnonymousTests.swift in Sources */,
7044C1EC25C5CC930011F6E7 /* ParseOperationCombineTests.swift in Sources */,
70C7DC1E24D20E530050419B /* ParseUserTests.swift in Sources */,
707CCE9C25FBD4D0003C3B64 /* ParseUserOAuthTests.swift in Sources */,
70A2D81F25B36A7D001BEB7D /* ParseAuthenticationTests.swift in Sources */,
70D1BE4B25BB312700A42E7C /* ParseConfigTests.swift in Sources */,
911DB13324C494390027F3C7 /* MockURLProtocol.swift in Sources */,
Expand Down Expand Up @@ -1853,6 +1859,7 @@
70A2D86D25B3ADB6001BEB7D /* ParseAnonymousTests.swift in Sources */,
7044C1EE25C5CC930011F6E7 /* ParseOperationCombineTests.swift in Sources */,
709B98582556ECAA00507778 /* AnyEncodableTests.swift in Sources */,
707CCE9E25FBD4D0003C3B64 /* ParseUserOAuthTests.swift in Sources */,
70A2D82125B36A7D001BEB7D /* ParseAuthenticationTests.swift in Sources */,
70D1BE5F25BB312A00A42E7C /* ParseConfigTests.swift in Sources */,
709B98542556ECAA00507778 /* ParseInstallationTests.swift in Sources */,
Expand Down Expand Up @@ -1907,6 +1914,7 @@
70A2D86C25B3ADB6001BEB7D /* ParseAnonymousTests.swift in Sources */,
7044C1ED25C5CC930011F6E7 /* ParseOperationCombineTests.swift in Sources */,
70F2E2BA254F283000B2EA5C /* ParseInstallationTests.swift in Sources */,
707CCE9D25FBD4D0003C3B64 /* ParseUserOAuthTests.swift in Sources */,
70A2D82025B36A7D001BEB7D /* ParseAuthenticationTests.swift in Sources */,
70D1BE5525BB312900A42E7C /* ParseConfigTests.swift in Sources */,
70F2E2B9254F283000B2EA5C /* KeychainStoreTests.swift in Sources */,
Expand Down
19 changes: 17 additions & 2 deletions Sources/ParseSwift/API/API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public struct API {
case object(className: String, objectId: String)
case users
case user(objectId: String)
case refresh
case installations
case installation(objectId: String)
case sessions
Expand All @@ -29,6 +30,7 @@ public struct API {
case role(objectId: String)
case login
case logout
case revoke
case file(fileName: String)
case passwordReset
case verificationEmail
Expand All @@ -50,6 +52,8 @@ public struct API {
return "/users"
case .user(let objectId):
return "/users/\(objectId)"
case .refresh:
return "/users/refresh"
case .installations:
return "/installations"
case .installation(let objectId):
Expand All @@ -66,6 +70,8 @@ public struct API {
return "/login"
case .logout:
return "/logout"
case .revoke:
return "/revoke"
case .file(let fileName):
return "/files/\(fileName)"
case .passwordReset:
Expand Down Expand Up @@ -117,6 +123,9 @@ public struct API {
/// Specify tags.
/// - note: This is typically used indirectly by `ParseFile`.
case tags([String: String])
/// Remove mimeType.
/// - note: This is typically used indirectly by `ParseUser` OAuth20.
case removeAccessToken

public func hash(into hasher: inout Hasher) {
switch self {
Expand All @@ -136,6 +145,8 @@ public struct API {
hasher.combine(7)
case .tags:
hasher.combine(8)
case .removeAccessToken:
hasher.combine(9)
}
}
}
Expand All @@ -148,8 +159,10 @@ public struct API {
headers["X-Parse-Client-Key"] = clientKey
}

if let token = BaseParseUser.currentUserContainer?.sessionToken {
headers["X-Parse-Session-Token"] = token
if let accessToken = BaseParseUser.currentUserContainer?.accessToken {
headers["X-Parse-Session-Token"] = accessToken
} else if let sessioinToken = BaseParseUser.currentUserContainer?.sessionToken {
headers["X-Parse-Session-Token"] = sessioinToken
}

if let installationId = BaseParseInstallation.currentInstallationContainer.installationId {
Expand Down Expand Up @@ -180,6 +193,8 @@ public struct API {
tags.forEach {(key, value) -> Void in
headers[key] = value
}
case .removeAccessToken:
headers.removeValue(forKey: "X-Parse-Session-Token")
}
}

Expand Down
14 changes: 10 additions & 4 deletions Sources/ParseSwift/API/Responses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ internal struct SaveResponse: Decodable {
}

internal struct UpdateSessionTokenResponse: Decodable {
var updatedAt: Date
let sessionToken: String
var updatedAt: Date?
let sessionToken: String?
let accessToken: String?
let refreshToken: String?
let expiresAt: Date?
}

internal struct UpdateResponse: Decodable {
Expand Down Expand Up @@ -87,10 +90,13 @@ internal struct QueryResponse<T>: Codable where T: ParseObject {
// MARK: ParseUser
internal struct LoginSignupResponse: Codable {
let createdAt: Date
let objectId: String
let sessionToken: String
var updatedAt: Date?
let objectId: String
let username: String?
let sessionToken: String?
let accessToken: String?
let refreshToken: String?
let expiresAt: Date?
}

// MARK: ParseFile
Expand Down
31 changes: 29 additions & 2 deletions Sources/ParseSwift/Objects/ParseUser+combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,39 @@ public extension ParseUser {
using *current*.

- parameter sessionToken: The sessionToken of the user to login.
- parameter accessToken: The OAuth2.0 accessToken of the user to login.
- parameter refreshToken: The OAuth2.0 refreshToken of the user for refreshing.
- parameter The date the OAuth2.0 sessionToken expires.
- 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 becomePublisher(sessionToken: String, options: API.Options = []) -> Future<Self, ParseError> {
func becomePublisher(sessionToken: String? = nil,
accessToken: String? = nil,
refreshToken: String? = nil,
expiresAt: Date? = nil,
options: API.Options = []) -> Future<Self, ParseError> {
Future { promise in
self.become(sessionToken: sessionToken, options: options, completion: promise)
self.become(sessionToken: sessionToken,
accessToken: accessToken,
refreshToken: refreshToken,
expiresAt: expiresAt,
options: options,
completion: promise)
}
}

// MARK: Refreshing SessionToken - Combine
/**
Refreshes the OAuth2.0 tokens of the currently logged in
user *asynchronously*. Publishes when complete.

This will also update the session in the Keychain.
- 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.
*/
static func refreshPublisher(options: API.Options = []) -> Future<Self, ParseError> {
Future { promise in
Self.refresh(options: options, completion: promise)
}
}

Expand Down
Loading