Firebase authorization
This commit is contained in:
parent
5fd6bf65f3
commit
523fb5d411
@ -24,5 +24,21 @@
|
||||
stopOnStyle = "0">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "143FAB5A-C171-426A-A0BA-67C44E72B9D5"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "AutoCat/Controllers/GoogleSignInController.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "61"
|
||||
endingLineNumber = "61"
|
||||
landmarkName = "webView(_:decidePolicyFor:decisionHandler:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
|
||||
@ -19,8 +19,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
let config = Realm.Configuration(
|
||||
schemaVersion: 3,
|
||||
schemaVersion: 5,
|
||||
migrationBlock: { migration, oldSchemaVersion in
|
||||
if oldSchemaVersion <= 3 {
|
||||
var numbers: [String] = []
|
||||
migration.enumerateObjects(ofType: "Vehicle") { old, new in
|
||||
if let number = old?["number"] as? String {
|
||||
if numbers.contains(number) {
|
||||
migration.delete(old!)
|
||||
} else {
|
||||
numbers.append(number)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Realm.Configuration.defaultConfiguration = config
|
||||
|
||||
@ -6,7 +6,7 @@ import SwiftDate
|
||||
import RxRealm
|
||||
import RxDataSources
|
||||
|
||||
class CheckController: UIViewController, MaskedTextFieldDelegateListener {
|
||||
class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITableViewDelegate {
|
||||
|
||||
@IBOutlet weak var number: UITextField!
|
||||
@IBOutlet weak var check: UIButton!
|
||||
@ -32,7 +32,7 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener {
|
||||
} else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
})
|
||||
}, canEditRowAtIndexPath: { _, _ in true })
|
||||
|
||||
ds.titleForHeaderInSection = { dataSourse, index in
|
||||
return dataSourse.sectionModels[index].header
|
||||
@ -42,11 +42,14 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener {
|
||||
.subscribe(onNext: self.updateDetailController(with:))
|
||||
.disposed(by: self.bag)
|
||||
|
||||
|
||||
Observable.collection(from: realm.objects(Vehicle.self)
|
||||
.sorted(byKeyPath: "addedDate", ascending: false))
|
||||
.map { $0.groupedByDate() }
|
||||
.bind(to: self.history.rx.items(dataSource: ds))
|
||||
.disposed(by: self.bag)
|
||||
|
||||
self.history.rx.setDelegate(self).disposed(by: self.bag)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
@ -111,7 +114,7 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener {
|
||||
func onReceivedVehicle(_ vehicle: Vehicle) {
|
||||
if let realm = try? Realm() {
|
||||
try? realm.write {
|
||||
realm.add(vehicle)
|
||||
realm.add(vehicle, update: .modified)
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,5 +141,21 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener {
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
guard let vehicle: Vehicle = try? self.history.rx.model(at: indexPath) else { return nil }
|
||||
|
||||
let updateAction = UIContextualAction(style: .normal, title: "Update") { action, view, completion in
|
||||
IHProgressHUD.show()
|
||||
Api.checkVehicle(by: vehicle.number, force: true)
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribe(onNext: self.onReceivedVehicle(_:), onError: { err in
|
||||
IHProgressHUD.showError(withStatus: err.localizedDescription)
|
||||
print(err.localizedDescription)
|
||||
}).disposed(by: self.bag)
|
||||
completion(true)
|
||||
}
|
||||
updateAction.image = UIImage(systemName: "arrow.2.circlepath")
|
||||
updateAction.backgroundColor = .systemBlue
|
||||
return UISwipeActionsConfiguration(actions: [updateAction])
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import UIKit
|
||||
import WebKit
|
||||
import CommonCrypto
|
||||
import RxSwift
|
||||
|
||||
private struct TokenResponse: Codable {
|
||||
struct TokenResponse: Codable {
|
||||
var id_token: String
|
||||
var refresh_token: String
|
||||
var refresh_token: String?
|
||||
var access_token: String
|
||||
var expires_in: Int
|
||||
var token_type: String
|
||||
@ -14,6 +15,7 @@ private struct TokenResponse: Codable {
|
||||
class GoogleSignInController: UIViewController, WKNavigationDelegate {
|
||||
@IBOutlet weak var webView: WKWebView!
|
||||
|
||||
private var bag = DisposeBag()
|
||||
private var codeVerifier: String = ""
|
||||
public var completion: (() -> Void)?
|
||||
|
||||
@ -35,7 +37,7 @@ class GoogleSignInController: UIViewController, WKNavigationDelegate {
|
||||
+ "&code_challenge_method=S256"
|
||||
+ "&scope=email%20profile"
|
||||
+ "&redirect_uri=" + Constants.googleRedirectURL
|
||||
+ "&client_id=" + Constants.googleClientId
|
||||
+ "&client_id=" + Constants.fbClientId
|
||||
+ "&code_challenge=" + codeChallenge
|
||||
|
||||
if let url = URL(string: authUrlString) {
|
||||
@ -50,23 +52,15 @@ class GoogleSignInController: UIViewController, WKNavigationDelegate {
|
||||
if let queryItems = components.queryItems {
|
||||
if let code = queryItems.first(where: { $0.name == "code" })?.value {
|
||||
decisionHandler(.cancel)
|
||||
self.getToken(code: code) { error, idToken, refreshToken in
|
||||
if let err = error {
|
||||
DispatchQueue.main.async {
|
||||
self.dismiss(animated: true) {
|
||||
IHProgressHUD.showError(withStatus: err.localizedDescription)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Settings.shared.user.googleIdToken = idToken
|
||||
Settings.shared.user.googleRefreshToken = refreshToken
|
||||
DispatchQueue.main.async {
|
||||
self.dismiss(animated: true) {
|
||||
self.completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.getToken(code: code)
|
||||
.flatMap(fbVerifyAssertion)
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribe(onNext: { _ in
|
||||
self.dismiss(animated: true, completion: self.completion)
|
||||
}, onError: { error in
|
||||
IHProgressHUD.showError(withStatus: error.localizedDescription)
|
||||
})
|
||||
.disposed(by: self.bag)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -91,33 +85,65 @@ class GoogleSignInController: UIViewController, WKNavigationDelegate {
|
||||
return String(data: Data(Base64FS.encode(data: hash)), encoding: .utf8)?.trimmingCharacters(in: CharacterSet(charactersIn: "="))
|
||||
}
|
||||
|
||||
func getToken(code: String, completion: @escaping (Error?, String?, String?) -> Void) {
|
||||
func getToken(code: String) -> Observable<TokenResponse> {
|
||||
let tokenUrlString = Constants.googleTokenURL
|
||||
+ "?grant_type=authorization_code"
|
||||
+ "&code=" + code
|
||||
+ "&redirect_uri=" + Constants.googleRedirectURL
|
||||
+ "&client_id=" + Constants.googleClientId
|
||||
+ "&client_id=" + Constants.fbClientId
|
||||
+ "&code_verifier=" + self.codeVerifier
|
||||
|
||||
if let url = URL(string: tokenUrlString) {
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
if let data = data {
|
||||
if let resp = try? JSONDecoder().decode(TokenResponse.self, from: data) {
|
||||
completion(nil, resp.id_token, resp.refresh_token)
|
||||
} else {
|
||||
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Response parsing error"])
|
||||
completion(error, nil, nil)
|
||||
return URLSession.shared.rx.data(request: request).map { data in
|
||||
return try JSONDecoder().decode(TokenResponse.self, from: data)
|
||||
}
|
||||
} else {
|
||||
completion(error, nil, nil)
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
} else {
|
||||
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Bad URL"])
|
||||
completion(error, nil, nil)
|
||||
return Observable.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func fbVerifyAssertion(tokenResp: TokenResponse) -> Observable<Void> {
|
||||
let signupUrl = Constants.fbVerifyAssertion + "?key=" + (Constants.fbApiKey.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? Constants.fbApiKey)
|
||||
if let url = URL(string: signupUrl) {
|
||||
let innerBody = "providerId=google.com"
|
||||
+ "&id_token=" + tokenResp.id_token
|
||||
+ "&access_token=" + tokenResp.access_token
|
||||
|
||||
let body: [String:Any] = [
|
||||
"returnIdpCredential": true,
|
||||
"returnSecureToken": true,
|
||||
"autoCreate": true,
|
||||
"requestUri": "http://localhost",
|
||||
"postBody": innerBody
|
||||
]
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
|
||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.addValue(Constants.fbClientVersion, forHTTPHeaderField: "X-Client-Version")
|
||||
request.addValue(Constants.vin01BUndleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
|
||||
request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent")
|
||||
|
||||
return URLSession.shared.rx.json(request: request).map { response in
|
||||
guard let json = response as? [String: Any] else { return }
|
||||
if let newToken = json["idToken"] as? String {
|
||||
Settings.shared.user.googleIdToken = newToken
|
||||
print("Token: \(newToken)")
|
||||
}
|
||||
if let newRefreshToken = json["refreshToken"] as? String {
|
||||
Settings.shared.user.googleRefreshToken = newRefreshToken
|
||||
print("Refresh token: \(newRefreshToken)")
|
||||
}
|
||||
}.catchError { err in
|
||||
print(err)
|
||||
return .just(())
|
||||
}
|
||||
} else {
|
||||
return .just(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,8 @@
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>avto-nomer.ru</key>
|
||||
|
||||
@ -41,7 +41,6 @@ class VehiclePhoto: Object, Decodable {
|
||||
}
|
||||
|
||||
class Vehicle: Object, Decodable, IdentifiableType {
|
||||
@objc dynamic var _id: String = ""
|
||||
@objc dynamic var brand: VehicleBrand?
|
||||
@objc dynamic var model: VehicleModel?
|
||||
@objc dynamic var color: String?
|
||||
@ -59,10 +58,9 @@ class Vehicle: Object, Decodable, IdentifiableType {
|
||||
@objc dynamic var addedBy: String = ""
|
||||
let photos = List<VehiclePhoto>()
|
||||
|
||||
var identity: String { _id }
|
||||
var identity: String { number }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case _id
|
||||
case brand
|
||||
case model
|
||||
case color
|
||||
@ -83,7 +81,6 @@ class Vehicle: Object, Decodable, IdentifiableType {
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self._id = try container.decode(String.self, forKey: ._id)
|
||||
self.brand = try container.decodeIfPresent(VehicleBrand.self, forKey: .brand)
|
||||
self.model = try container.decodeIfPresent(VehicleModel.self, forKey: .model)
|
||||
self.color = try container.decodeIfPresent(String.self, forKey: .color)
|
||||
@ -111,7 +108,10 @@ class Vehicle: Object, Decodable, IdentifiableType {
|
||||
|
||||
init(_ number: String) {
|
||||
self.number = number
|
||||
self._id = UUID().uuidString
|
||||
self.addedDate = Date().timeIntervalSince1970*1000
|
||||
}
|
||||
|
||||
override static func primaryKey() -> String? {
|
||||
return "number"
|
||||
}
|
||||
}
|
||||
|
||||
57
AutoCat/ThirdParty/Api.swift
vendored
57
AutoCat/ThirdParty/Api.swift
vendored
@ -2,7 +2,7 @@ import Foundation
|
||||
import RxSwift
|
||||
|
||||
class Api {
|
||||
private static let baseUrl = "https://vps.aliencat.pro:8443/"
|
||||
private static let baseUrl = "http://127.0.0.1:3000/" //"https://vps.aliencat.pro:8443/"
|
||||
|
||||
private static func genError(_ msg: String, suggestion: String, code: Int = 0) -> Error {
|
||||
return NSError(domain: "", code: code, userInfo: [NSLocalizedDescriptionKey: msg, NSLocalizedRecoverySuggestionErrorKey: suggestion])
|
||||
@ -36,7 +36,7 @@ class Api {
|
||||
return URLSession.shared.rx.data(request: request).map { data in
|
||||
// let str = String(data: data, encoding: .utf8)
|
||||
// print("================================")
|
||||
// print(str)
|
||||
// print(str?.replacingOccurrences(of: "\\\"", with: "\""))
|
||||
// print("================================")
|
||||
let resp = try JSONDecoder().decode(Response<T>.self, from: data)
|
||||
if resp.success {
|
||||
@ -47,6 +47,44 @@ class Api {
|
||||
}
|
||||
}
|
||||
|
||||
public static func refreshFbToken() -> Observable<Void> {
|
||||
guard let token = Settings.shared.user.googleIdToken, let refreshToken = Settings.shared.user.googleRefreshToken, let jwt = JWT(string: token), jwt.expired else {
|
||||
return .just(())
|
||||
}
|
||||
|
||||
let refreshUrlString = Constants.fbRefreshToken + "?key=" + Constants.fbApiKey
|
||||
|
||||
let body = [
|
||||
"grantType": "refresh_token",
|
||||
"refreshToken": refreshToken
|
||||
]
|
||||
|
||||
if let url = URL(string: refreshUrlString) {
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
|
||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.addValue(Constants.fbClientVersion, forHTTPHeaderField: "X-Client-Version")
|
||||
request.addValue(Constants.vin01BUndleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
|
||||
request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent")
|
||||
return URLSession.shared.rx.json(request: request).map { resp in
|
||||
guard let json = resp as? [String: Any] else { return }
|
||||
if let newToken = json["id_token"] as? String {
|
||||
Settings.shared.user.googleIdToken = newToken
|
||||
print("Token was successfully refresh to: \(newToken)")
|
||||
}
|
||||
if let newRefreshToken = json["refresh_token"] as? String {
|
||||
Settings.shared.user.googleRefreshToken = newRefreshToken
|
||||
}
|
||||
}.catchError { error in
|
||||
print(error)
|
||||
return .just(())
|
||||
}
|
||||
} else {
|
||||
return .just(())
|
||||
}
|
||||
}
|
||||
|
||||
public static func login(username: String, password: String) -> Observable<User> {
|
||||
let body = [
|
||||
"login": username,
|
||||
@ -71,14 +109,19 @@ class Api {
|
||||
return self.makeRequest(api: "vehicles", method: "GET", body: body)
|
||||
}
|
||||
|
||||
public static func checkVehicle(by number: String) -> Observable<Vehicle> {
|
||||
if let token = Settings.shared.user.googleIdToken, let jwt = JWT(string: token) {
|
||||
|
||||
public static func checkVehicle(by number: String, force: Bool = false) -> Observable<Vehicle> {
|
||||
return self.refreshFbToken().flatMap { () -> Observable<Vehicle> in
|
||||
var body = [
|
||||
"number": number,
|
||||
"forceUpdate": String(force)
|
||||
]
|
||||
if let token = Settings.shared.user.googleIdToken {
|
||||
body["googleIdToken"] = token
|
||||
}
|
||||
|
||||
return self.makeRequest(api: "vehicles/check", method: "POST", body: ["number": number]).map { (vehicle: Vehicle) -> Vehicle in
|
||||
return self.makeRequest(api: "vehicles/check", method: "POST", body: body).map { (vehicle: Vehicle) -> Vehicle in
|
||||
vehicle.addedDate = Date().timeIntervalSince1970*1000
|
||||
return vehicle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,13 @@ enum Constants {
|
||||
static let googleTokenURL = "https://oauth2.googleapis.com/token"
|
||||
static let googleRedirectURL = "com.googleusercontent.apps.994679674451-k7clunkk4nicl6iuajdtc5u7hvustbdb:/oauth2callback"
|
||||
|
||||
static let googleClientId = "994679674451-k7clunkk4nicl6iuajdtc5u7hvustbdb.apps.googleusercontent.com"
|
||||
static let googleApiKey = "AIzaSyDVlrQj_05y6AeZNf8enpSWFIiHhgwfnGI"
|
||||
static let fbClientId = "994679674451-k7clunkk4nicl6iuajdtc5u7hvustbdb.apps.googleusercontent.com"
|
||||
static let fbApiKey = "AIzaSyDVlrQj_05y6AeZNf8enpSWFIiHhgwfnGI"
|
||||
|
||||
static let fbClientVersion = "iOS/FirebaseSDK/6.5.1/FirebaseCore-iOS"
|
||||
static let fbVerifyAssertion = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAssertion"
|
||||
static let fbRefreshToken = "https://securetoken.googleapis.com/v1/token"
|
||||
static let fbUserAgent = "FirebaseAuth.iOS/6.5.1 ru.Vin01/1.0 iPhone/13.5 hw/sim"
|
||||
|
||||
static let vin01BUndleId = "ru.Vin01"
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user