Skip to content

Commit

Permalink
Merge pull request #298 from mohssenfathi/token-storage
Browse files Browse the repository at this point in the history
Token Storage + UI
  • Loading branch information
mohssenfathi authored May 9, 2024
2 parents a9fa6fd + 1c373d8 commit 8f927ed
Show file tree
Hide file tree
Showing 39 changed files with 1,586 additions and 665 deletions.
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let package = Package(
name: "rides-ios-sdk",
defaultLocalization: "en",
platforms: [
.iOS(.v13)
.iOS(.v15)
],
products: [
.library(
Expand All @@ -34,6 +34,9 @@ let package = Package(
name: "UberAuth",
dependencies: [
"UberCore"
],
resources: [
.process("Resources")
]
),
.target(
Expand Down
28 changes: 21 additions & 7 deletions Sources/UberAuth/Authorize/AuthorizationCodeAuthProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {

private let networkProvider: NetworkProviding

private let tokenManager: TokenManaging

// MARK: Initializers

public init(presentationAnchor: ASPresentationAnchor = .init(),
Expand All @@ -60,6 +62,7 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
self.responseParser = AuthorizationCodeResponseParser()
self.shouldExchangeAuthCode = shouldExchangeAuthCode
self.networkProvider = NetworkProvider(baseUrl: Constants.baseUrl)
self.tokenManager = TokenManager()
}

init(presentationAnchor: ASPresentationAnchor = .init(),
Expand All @@ -68,7 +71,8 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
configurationProvider: ConfigurationProviding = DefaultConfigurationProvider(),
applicationLauncher: ApplicationLaunching = UIApplication.shared,
responseParser: AuthorizationCodeResponseParsing = AuthorizationCodeResponseParser(),
networkProvider: NetworkProviding = NetworkProvider(baseUrl: Constants.baseUrl)) {
networkProvider: NetworkProviding = NetworkProvider(baseUrl: Constants.baseUrl),
tokenManager: TokenManaging = TokenManager()) {

guard let clientID: String = configurationProvider.clientID else {
preconditionFailure("No clientID specified in Info.plist")
Expand All @@ -86,6 +90,7 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
self.responseParser = responseParser
self.shouldExchangeAuthCode = shouldExchangeAuthCode
self.networkProvider = networkProvider
self.tokenManager = tokenManager
}

// MARK: AuthProviding
Expand Down Expand Up @@ -340,6 +345,12 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
switch result {
case .success(let response):
let client = Client(tokenResponse: response)
if let accessToken = client.accessToken {
self?.tokenManager.saveToken(
accessToken,
identifier: TokenManager.defaultAccessTokenIdentifier
)
}
completion(.success(client))
case .failure(let error):
completion(.failure(error))
Expand All @@ -361,12 +372,15 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
fileprivate extension Client {

init(tokenResponse: TokenRequest.Response) {
self = .init(
accessToken: tokenResponse.accessToken,
refreshToken: tokenResponse.refreshToken,
tokenType: tokenResponse.tokenType,
expiresIn: tokenResponse.expiresIn,
scope: tokenResponse.scope
self = Client(
authorizationCode: nil,
accessToken: AccessToken(
tokenString: tokenResponse.tokenString,
refreshToken: tokenResponse.refreshToken,
tokenType: tokenResponse.tokenType,
expiresIn: tokenResponse.expiresIn,
scope: tokenResponse.scope
)
)
}
}
169 changes: 169 additions & 0 deletions Sources/UberAuth/Button/LoginButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//
// Copyright © Uber Technologies, Inc. All rights reserved.
//


import Foundation
import UberCore
import UIKit


///
/// A protocol to respond to Uber LoginButton events
///
public protocol LoginButtonDelegate: AnyObject {

///
/// The login button attempted to log out
///
/// - Parameters:
/// - button: The LoginButton instance that attempted logout
/// - success: A bollean indicating whether or not the logout was successful
func loginButton(_ button: LoginButton,
didLogoutWithSuccess success: Bool)


///
/// The login button completed authentication
///
/// - Parameters:
/// - button: The LoginButton instance that completed authentication
/// - result: A Result containing the authentication response. If successful, contains the Client object returned from the UberAuth authenticate function. If failed, contains an UberAuth error indicating the failure reason.
func loginButton(_ button: LoginButton,
didCompleteLoginWithResult result: Result<Client, UberAuthError>)
}

///
/// A protocol to provide content for the Uber LoginButton
///
public protocol LoginButtonDataSource: AnyObject {

///
/// Provides an optional AuthContext to be used during authentication
///
/// - Parameter button: The LoginButton instance requesting the information
/// - Returns: An optional AuthContext instance
func authContext(_ button: LoginButton) -> AuthContext
}

public final class LoginButton: UberButton {

// MARK: Public Properties

/// The LoginButtonDelegate for this button
public weak var delegate: LoginButtonDelegate?

public weak var dataSource: LoginButtonDataSource?

// MARK: Private Properties

private var buttonState: State {
tokenManager.getToken(
identifier: Constants.tokenIdentifier
) != nil ? .loggedIn : .loggedOut
}

private let tokenManager: TokenManaging

// MARK: Initializers

public override init(frame: CGRect) {
self.tokenManager = TokenManager()
super.init(frame: frame)
configure()
}

public required init?(coder: NSCoder) {
self.tokenManager = TokenManager()
super.init(coder: coder)
configure()
}

public init(tokenManager: TokenManaging = TokenManager()) {
self.tokenManager = tokenManager
super.init(frame: .zero)
configure()
}

// MARK: UberButton

override public var title: String {
buttonState.title
}

override public var image: UIImage? {
UIImage(
named: "uber_logo_white",
in: .module,
compatibleWith: nil
)?.withRenderingMode(.alwaysTemplate)
}

// MARK: Private

private func configure() {
addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
}

@objc private func buttonTapped(_ sender: UIButton) {
switch buttonState {
case .loggedIn:
logout()
case .loggedOut:
login()
}
}

private func login() {
let defaultContext = AuthContext(
authProvider: .authorizationCode(
shouldExchangeAuthCode: true
)
)
let context = dataSource?.authContext(self) ?? defaultContext
UberAuth.login(context: context) { [weak self] result in
guard let self else { return }
delegate?.loginButton(self, didCompleteLoginWithResult: result)
update()
}
}

private func logout() {
// TODO: Implement UberAuth.logout()
tokenManager.deleteToken(identifier: Constants.tokenIdentifier)
update()
}

// MARK: State

enum State {
case loggedIn
case loggedOut

var title: String {
switch self {
case .loggedIn:
return NSLocalizedString(
"Sign Out",
bundle: .module,
comment: "Login Button Sign Out Description"
)
.uppercased()
case .loggedOut:
return NSLocalizedString(
"Sign In",
bundle: .module,
comment: "Login Button Sign In Description"
)
.uppercased()
}
}
}

// MARK: Constants

private enum Constants {
static let tokenIdentifier: String = TokenManager.defaultAccessTokenIdentifier
}
}

50 changes: 26 additions & 24 deletions Sources/UberAuth/Client.swift
Original file line number Diff line number Diff line change
@@ -1,40 +1,45 @@
//
// Copyright © Uber Technologies, Inc. All rights reserved.
// Client.swift
// UberAuth
//
// Copyright © 2024 Uber Technologies, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.


import Foundation
import UberCore

public struct Client: Equatable {

// MARK: Properties

public let authorizationCode: String?

public let accessToken: String?

public let refreshToken: String?

public let tokenType: String?

public let expiresIn: Int?

public let scope: [String]?
public let accessToken: AccessToken?

// MARK: Initializers

public init(authorizationCode: String? = nil,
accessToken: String? = nil,
refreshToken: String? = nil,
tokenType: String? = nil,
expiresIn: Int? = nil,
scope: [String]? = nil) {
accessToken: AccessToken? = nil) {
self.authorizationCode = authorizationCode
self.accessToken = accessToken
self.refreshToken = refreshToken
self.tokenType = tokenType
self.expiresIn = expiresIn
self.scope = scope
}
}

Expand All @@ -43,11 +48,8 @@ extension Client: CustomStringConvertible {
public var description: String {
return """
Authorization Code: \(authorizationCode ?? "nil")
Access Token: \(accessToken ?? "nil")
Refresh Token: \(refreshToken ?? "nil")
Token Type: \(tokenType ?? "nil")
Expires In: \(expiresIn ?? -1)
Scopes: \(scope?.joined(separator: ", ") ?? "nil")
Access Token:
\(accessToken?.description ?? "nil")
"""
}
}
6 changes: 6 additions & 0 deletions Sources/UberAuth/Resources/Media.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 8f927ed

Please sign in to comment.