// // File.swift // uSTADIUM // // Created by Sonny Trujillo Jr on 8/13/18. // Copyright © 2018 uSTADIUM. All rights reserved. // import Foundation import KeychainSwift import FirebaseAuth import FirebaseCrashlytics import Alamofire import AppsFlyerLib /// Auth Service singleton class for handling all authentication requests. final class AuthService { /// Authentication state. enum State { case banned(LoginOBJ?) case loading case loggedOut(Error?) case loggingIn(URLSessionDataTask) case signingUp(URLSessionDataTask) case loggedIn(LoginOBJ) } static let change = Notification.Name(rawValue: "uSTADIUMAuthenticationChanged") static let shared = AuthService() /// The current Authentication state var state: AuthService.State = .loading { didSet { switch state { case .loggedIn(let obj): NSUbiquitousKeyValueStore.default.set(obj.data?.user.isBanned ?? false, forKey: "isBanned") NSUbiquitousKeyValueStore.default.synchronize() self.user = obj.data?.user if obj.data?.user.isBanned ?? false { self.state = .banned(obj) return } self.setTokens(token: obj.data?.token ?? "", firebase: obj.data?.firebaseToken ?? "") default: break } DispatchQueue.main.async { NotificationCenter.default.post(Notification(name: AuthService.change)) } } } /// The login object returned after a login. var object: LoginOBJ? { get { switch state { case .loggedIn(let obj): self.user = obj.data?.user return obj case .banned(let obj): return obj default: return nil } } } /// The user object, or nil if they are not logged in. var user: UserOBJ? { get { switch state { case .loggedIn(let obj): return obj.data?.user case .banned(let obj): return obj?.data?.user default: return nil } } set { } } var userPoints: Float? var profilePoints: Float? var tokenizationKey: String? /// The user's id, or nil if they are not logged in. var userID: Int? { get { return self.user?.id } } /// The auth token, or nil if they are not logged in. var authToken: String? { get { if let token = self.object?.data?.token { return "JWT \(token)" } return nil } } /// The firebase token, or nil if they are not logged in. var firebaseToken: String? { get { return self.object?.data?.firebaseToken } } var headers: HTTPHeaders? { get { if let authToken = AuthService.shared.authToken { return [ "Authorization": authToken ]; } return nil; } } /// True if the user is an admin, false otherwise. var isAdmin: Bool { get { if let roles = AuthService.shared.object?.data?.user.roles { let rs = roles.filter{ $0.id == 1 } return rs.count > 0 } return false } } private var _canSendPush: Bool? var canSendPush: Bool { get { if let csp = _canSendPush { return csp } if let roles = AuthService.shared.object?.data?.user.roles { let rs = roles.filter{ $0.id == 1 || $0.id == 3 } if rs.count > 0 { self._canSendPush = true return true } } if let u = AuthService.shared.object?.data?.user, let feeds = AuthService.shared.object?.data?.user.feeds { for f in feeds { if f.leaders.contains(u) { self._canSendPush = true return true } } } self._canSendPush = false return false } } var isBanned: Bool { get { switch self.state { case .banned: return true default: break } guard let user = AuthService.shared.user else { return false } return user.isBanned ?? false } } init(){ self.reload{ _, _ in } } func updateUser(_ user: UserOBJ){ var obj = self.object obj?.update(user: user) if let o = obj { self.state = .loggedIn(o) } } // MARK: - Signup /// Signsup a new account with the given information. /// /// Calls ot this method are only valid when state is not .loggingIn or .signingUp func signup(data: AccountSignup, _ completionHandler: @escaping (_ result: LoginOBJ?, _ error: Error?)->()) { switch state { case .loggingIn(_): return case .signingUp(_): return default: break } self.validUsernames = [:] self.validEmail = [:] setLoginCredentials(username: data.username, password: data.password) let jsonData = try! JSONEncoder().encode(data) var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/signup")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" request.httpBody = jsonData let task = URLSession.shared.dataTask(with: request){ [unowned self, completionHandler] data, response, error in if let error = error { USTracker.tracker.trackEvent(path: .preLogout, data: ["description" : "signup_method", "event_data" : "\(error.localizedDescription)"]) self.state = .loggedOut(error) completionHandler(nil, error) return } guard let data = data else { USTracker.tracker.trackEvent(path: .preLogout, data: ["description" : "signup_method", "event_data" : "data empty"]) self.state = .loggedOut(error) completionHandler(nil, nil) return } do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 let object = try decoder.decode(LoginOBJ.self, from: data) as LoginOBJ if let data = object.data { self.checkForReferrence(userid: data.user.id) self.state = .loggedIn(object) completionHandler(object, nil) USTracker.tracker.trackEvent(path: .signUp) } else { self.state = .loggedOut(nil) completionHandler(nil, nil) } } catch { Crashlytics.crashlytics().record(error:error) self.state = .loggedOut(error) completionHandler(nil, error) } } self.state = .signingUp(task) task.resume() } func checkForReferrence(userid: Int){ let referredBy = UserDefaults.getReferredBy() if referredBy == 0 || (referredBy == userid){ return } let presenter = RewardPresenter() presenter.referralReward(referredById: referredBy, referredId: userid) } // MARK: - Login /// Submits a request to any of the Auth endpoints that return valid login responses. /// /// Calls ot this method are only valid when state is not .loggingIn or .signingUp private func login(request: URLRequest, _ completionHandler: @escaping (_ result: LoginOBJ?, _ error: Error?)->()) { switch state { case .loggingIn(_): return case .signingUp(_): return default: break } let task = URLSession.shared.dataTask(with: request){ [unowned self, completionHandler] data, response, error in if let error = error { USTracker.tracker.trackEvent(path: .preLogout, data: ["description" : "login_method", "event_data" : "\(error.localizedDescription)"]) self.logout() self.state = .loggedOut(error) completionHandler(nil, error) return } guard let data = data else { USTracker.tracker.trackEvent(path: .preLogout, data: ["description" : "login_method", "event_data" : "data is nil"]) self.logout() self.state = .loggedOut(nil) completionHandler(nil, nil) return } let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 do { let result = try decoder.decode(LoginOBJ.self, from: data) as LoginOBJ print("RESULT: \(result)") self.state = .loggedIn(result) completionHandler(result, nil) USTracker.tracker.trackEvent(path: .login) } catch { if let apiError = try? decoder.decode(APIError.self, from: data) as APIError, let isBanned = apiError.data?.isBanned, isBanned == true { self.state = .banned(nil) completionHandler(nil, nil) return } USTracker.tracker.trackEvent(path: .preLogout, data: ["description" : "login_method - decoding error", "event_data" : "\(error.localizedDescription)"]) Crashlytics.crashlytics().record(error: error) self.logout() self.state = .loggedOut(error) completionHandler(nil, error) } } self.state = .loggingIn(task) task.resume() } /// Logs into uSTADIUM with a username and password. func loginWithCustom(username: String, password: String, referral: URL?, feeds: [Int], _ completionHandler: @escaping (_ result: LoginOBJ?, _ error: Error?)->()) { var auth = CustomAuth(username: username, password: password, feeds: feeds) if let url = referral { let action = feeds.count > 0 ? "subscribe" : "registered"; auth.referral = AccountReferral(link: url, action: action, accountResult: .loggedIn) } let jsonData = try! JSONEncoder().encode(auth) setLoginCredentials(username: username, password: password) var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/token")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" request.httpBody = jsonData self.login(request: request, completionHandler) } /// Logs into uSTADIUM through Twitter. func loginWithTwitter(authToken: String, authTokenSecret: String, userId: String, referral: URL?, feeds: [Int], _ completionHandler: @escaping (_ result: LoginOBJ?, _ error: Error?)->()){ var tw = TwitterAuth(oauth_token: authToken, oauth_secret: authTokenSecret, user_id: userId, feeds: feeds) if let url = referral { let action = feeds.count > 0 ? "subscribe" : "registered"; tw.referral = AccountReferral(link: url, action: action, accountResult: .loggedIn) } let jsonData = try! JSONEncoder().encode(tw) var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/twitter")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" request.httpBody = jsonData self.login(request: request, completionHandler) } /// Logs into uSTADIUM using a stored auth token. func reloadWithToken(token: String, _ completionHandler: @escaping (_ result: LoginOBJ?, _ error: Error?)->()) { let tokenReload = TokenReload() let jsonData = try! JSONEncoder().encode(tokenReload) var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/loginWithToken")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.setValue(token, forHTTPHeaderField: "Authorization") request.httpMethod = "POST" request.httpBody = jsonData self.login(request: request, completionHandler) } /// Logout func logout(deviceToken: String, _ completionHandler: @escaping (_ result: Data?, _ error: Error?) -> Void) { let jsonData = try? JSONSerialization.data(withJSONObject: ["deviceToken" : deviceToken]) var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/logout")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.setValue(self.authToken, forHTTPHeaderField: "Authorization") request.httpMethod = "POST" request.httpBody = jsonData let task = URLSession.shared.dataTask(with: request){ data, response, error in completionHandler(data, error) } task.resume() } // MARK: - PN Token Registration func registerToken(token: String){ guard let authToken = AuthService.shared.authToken else { return } let reg = RegisterDevice(token: token) let jsonData = try! JSONEncoder().encode(reg) var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/device")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.setValue(authToken, forHTTPHeaderField: "Authorization") request.httpMethod = "POST" request.httpBody = jsonData URLSession.shared.dataTask(with: request){ data, response, error in /* Nothing. We're done! */ }.resume() } // MARK: - Reset Forgot Password /// Submit's a password reset request. func resetForgottenPassword(code: String, password: String, username: String, _ completionHandler: @escaping (_ result: LoginOBJ?, _ error: Error?)->()) { let reset = ResetPasswordWithCode(code: code, username: username, password: password) let jsonData = try! JSONEncoder().encode(reset) var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/reset-forgotten-password")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" request.httpBody = jsonData let task = URLSession.shared.dataTask(with: request){ [unowned self, completionHandler] data, response, error in if let error = error { completionHandler(nil, error) return } guard let data = data else { completionHandler(nil, nil) return } do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 let object = try decoder.decode(LoginOBJ.self, from: data) as LoginOBJ self.state = .loggedIn(object) completionHandler(object, nil) USTracker.tracker.trackEvent(path: .resetPassword) } catch { Crashlytics.crashlytics().record(error:error) completionHandler(nil, error) } } task.resume() } /// Submits a password reset request func requestResetCode(username: String, email: String, _ completionHandler: @escaping (Bool, Error?)->()){ let forgot = ForgotPassword(username: username, email: email) let jsonData = try! JSONEncoder().encode(forgot) var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/forgot-password")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" request.httpBody = jsonData URLSession.shared.dataTask(with: request){ [completionHandler] data, response, error in if let error = error { completionHandler(false, error) return } var isValid = false if let httpResponse = response as? HTTPURLResponse { isValid = (httpResponse.statusCode == 200) } USTracker.tracker.trackEvent(path: .resetCode) completionHandler(isValid, nil) }.resume() } /// Submit's a password reset request. func requestForgottenUsername(email: String, _ completionHandler: @escaping (_ success: Bool, _ error: Error?)->()) { let reset = ForgotUsername(email: email) let jsonData = try! JSONEncoder().encode(reset) var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/forgot-username")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" request.httpBody = jsonData let task = URLSession.shared.dataTask(with: request){ [completionHandler] data, response, error in if let error = error { completionHandler(false, error) return } guard let _ = data else { completionHandler(false, nil) return } USTracker.tracker.trackEvent(path: .forgottenUserName) completionHandler(true, error) } task.resume() } // MARK: - Firebase func getFirebaseUID(_ completionHandler: @escaping ((String?, Error?) -> ())) { if let uid = Auth.auth().currentUser?.uid { completionHandler(uid, nil); } else if let firebaseToken = self.firebaseToken { Auth.auth().signIn(withCustomToken: firebaseToken ) { [weak self] (result, error) in guard let self = self else { return } guard error == nil else { self.refreshFirebaseToken(completionHandler) return } completionHandler(result?.user.uid, nil) } } else { self.refreshFirebaseToken(completionHandler) } } #warning("This should be removed, firebase cloud messagin token is not retrieved from the server") private func refreshFirebaseToken(_ completionHandler: @escaping ((String?, Error?) -> ())){ var components = URLComponents(string: "\(SERVER_URL)api/users/refresh-firebasetoken")! components.queryItems = [URLQueryItem(name: "project", value: "abd87")] let url = try! components.asURL() var request = URLRequest(url: url) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpMethod = "GET" let task = URLSession.shared.dataTask(with: request){ [unowned self, completionHandler] data, response, error in if let error = error { completionHandler(nil, error) return } guard let data = data else { completionHandler(nil, nil) return } do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 let result = try decoder.decode(RefreshFirebaseResponse.self, from: data) as RefreshFirebaseResponse var obj = self.object obj?.update(firebase: result.firebaseToken) if let obj = obj { self.state = .loggedIn(obj) } completionHandler(result.firebaseToken, nil) } catch { Crashlytics.crashlytics().record(error:error) completionHandler(nil, error) } } task.resume() } /// Attempts to reload the users account /// /// It starts by first attempting to reload with the stored token. /// If that does not work it will attempt to user the stored username and password. func reload(_ completionHandler: @escaping (_ result: LoginOBJ?, _ error: Error?)->()){ let keychain = KeychainSwift(keyPrefix: "uSTADIUM_") keychain.accessGroup = "28Y75BYDU7.com.uSTADIUM.uSTADIUM1" keychain.synchronizable = true let token = keychain.get("token") let username = keychain.get("username") let password = keychain.get("password") if let token = token { var tkn = token if !token.starts(with: "JWT ") { tkn = "JWT \(token)" } reloadWithToken(token: tkn){ result, error in if let err = error { if let u = username, let p = password { self.loginWithCustom(username: u, password: p, referral: nil, feeds: [], completionHandler) } else { completionHandler(nil, err) } } completionHandler(result, error) } } else if let u = username, let p = password { loginWithCustom(username: u, password: p, referral: nil, feeds: [], completionHandler) } else { USTracker.tracker.trackEvent(path: .preLogout, data: ["description" : "reload_method", "event_data":"switch_status_logged_out"]) self.state = .loggedOut(nil) completionHandler(nil, nil) } } // MARK: - Logout func logout() { USTracker.tracker.trackEvent(path: .logout) if let deviceToken = UserDefaults.standard.object(forKey: "ustadiumDeviceToken") as? String { self.logout(deviceToken: deviceToken) {_,_ in } } clearLoginCredentials() self.state = .loggedOut(nil) self.presentLogin() } /// Stores the Login credentials securely in the Keychain private func setLoginCredentials(username: String, password: String) { let keychain = KeychainSwift(keyPrefix: "uSTADIUM_") keychain.accessGroup = "28Y75BYDU7.com.uSTADIUM.uSTADIUM1" keychain.synchronizable = true keychain.set(username, forKey: "username") keychain.set(password, forKey: "password") } /// Stores the ustadium and firebase tokens securely in the Keychain private func setTokens(token: String, firebase: String){ let keychain = KeychainSwift(keyPrefix: "uSTADIUM_") keychain.accessGroup = "28Y75BYDU7.com.uSTADIUM.uSTADIUM1" keychain.synchronizable = true keychain.set(token, forKey: "token") keychain.set(firebase, forKey: "firebase") } /// Clears the keychain private func clearLoginCredentials() { USTracker.tracker.trackEvent(path: .preLogout, data: ["description" : "clear_keychain_credentials"]) let keychain = KeychainSwift(keyPrefix: "uSTADIUM_") keychain.accessGroup = "28Y75BYDU7.com.uSTADIUM.uSTADIUM1" keychain.synchronizable = true keychain.clear() } // MARK: - Validate var validUsernames: [String: (Bool, String)] = [:] var validEmail: [String: (Bool, String)] = [:] /// Checks if username is valid and available in the database. func valid(username: String, _ completionHandler: @escaping (Bool, String)->() ) { if username.isEmpty { completionHandler(false, "Username is required") return } if let (isValid, message) = validUsernames[username] { completionHandler(isValid, message) return } var allowedCharacters = CharacterSet.urlQueryAllowed allowedCharacters.remove(charactersIn: "!*'();:@&=+$,/?%#[]") let encoded = username.addingPercentEncoding(withAllowedCharacters: allowedCharacters) ?? "" var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/valid/username/\(encoded)")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpMethod = "GET" URLSession.shared.dataTask(with: request){ [unowned self, username, completionHandler] data, response, error in var isValid = false if let httpResponse = response as? HTTPURLResponse { isValid = (httpResponse.statusCode == 200) } guard let data = data else { self.validUsernames[username] = (isValid, "Username not valid") completionHandler(isValid, "Username not valid") return } if error != nil { self.validUsernames[username] = (isValid, "Username not valid") completionHandler(isValid, "Username not valid") return } do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 let result = try decoder.decode(ValidResponse.self, from: data) as ValidResponse self.validUsernames[username] = (isValid, result.message) completionHandler(isValid, result.message) } catch { Crashlytics.crashlytics().record(error:error) self.validUsernames[username] = (isValid, "Username not valid") completionHandler(isValid, "Username not valid") } }.resume() } /// From Apple Auth, get username for email, if no username then move to sign up func userNameForEmail(email: String, _ completionHandler: @escaping (String?) -> ()) { } /// Checks if email is valid and available in the database. func valid(email: String, _ completionHandler: @escaping (Bool, String)->() ) { if email.isEmpty { completionHandler(false, "Email is required") return } if let (isValid, message) = validEmail[email] { completionHandler(isValid, message) return } var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/valid/email/\(email)")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpMethod = "GET" URLSession.shared.dataTask(with: request){ [unowned self, email, completionHandler] data, response, error in var isValid = false if let httpResponse = response as? HTTPURLResponse { isValid = (httpResponse.statusCode == 200) } guard let data = data else { self.validEmail[email] = (isValid, "Email not valid") completionHandler(isValid, "Email not valid") return } if error != nil { self.validEmail[email] = (isValid, "Email not valid") completionHandler(isValid, "Email not valid") return } do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 let result = try decoder.decode(ValidResponse.self, from: data) as ValidResponse self.validEmail[email] = (isValid, result.message) completionHandler(isValid, result.message) } catch { Crashlytics.crashlytics().record(error:error) self.validEmail[email] = (isValid, "Email not valid") completionHandler(isValid, "Email not valid") } }.resume() } /// Presents the login view modally over any view thats currently being displayed. func presentLogin(){ DispatchQueue.main.async { guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } if let _ = UIApplication.topNavController() as? WelcomeNavigationController { return; } if let _ = UIApplication.topViewController() as? WelcomeViewController { return; } appDelegate.window?.rootViewController = UIStoryboard.signup.instantiateInitialViewController() appDelegate.window?.makeKeyAndVisible() } } private var generatedShareLink: URL? func defaultAppsFlyerSharedLink() -> URL { return URL(string: "https://ustadium-download.onelink.me/bm4V/3419a247")! } func getUserAppsflyerSharedLink(_ handler: @escaping (URL) -> ()) { AppsFlyerLib.shared().appInviteOneLinkID = "bm4V" guard let userID = AuthService.shared.userID else { handler(self.defaultAppsFlyerSharedLink()) return } guard let user = user else {return} AppsFlyerShareInviteHelper.generateInviteUrl(linkGenerator: {(_ generator: AppsFlyerLinkGenerator) -> AppsFlyerLinkGenerator in generator.setChannel("user_share") generator.setReferrerUID("\(userID)") generator.setReferrerName(user.username) generator.addParameterValue("\(userID)", forKey: "referredBy") generator.addParameterValue("5", forKey: "af_cost_value") // optional - set a brand domain to the user invite link return generator }, completionHandler: {(_ url: URL?) -> Void in handler(url!) }) } func getAffiliateDownloadLink(_ handler: @escaping (URL) -> ()) {AppsFlyerLib.shared().appInviteOneLinkID = "bm4V" guard let user = AuthService.shared.user else { handler(self.defaultAppsFlyerSharedLink()) return } FirebasePresenter().getPromoCodeInfo { code in guard let code = code else { handler(self.defaultAppsFlyerSharedLink()) return } AppsFlyerShareInviteHelper.generateInviteUrl(linkGenerator: {(_ generator: AppsFlyerLinkGenerator) -> AppsFlyerLinkGenerator in generator.setChannel("user_share") generator.setReferrerUID("\(user.id)") generator.setReferrerName(user.username) generator.addParameterValue("\(user.id)", forKey: "referredBy") generator.addParameterValue("5", forKey: "af_cost_value") generator.setDeeplinkPath("https://ustadium.com/deposit/\(code.code)") generator.addParameterValue("https://ustadium.com/deposit/\(code.code)", forKey: "af_dp") // optional - set a brand domain to the user invite link return generator }, completionHandler: {(_ url: URL?) -> Void in handler(url!) }) } } func shareLink() -> URL{ return URL(string: "https://ustadium-download.onelink.me/bm4V/3419a247")! } func setLastLogin() async throws { guard let user = self.user?.id else { return } guard let authToken = AuthService.shared.authToken else { return } var req = try URLRequest(url: URL(string: "\(SERVER_URL)api/users/\(user)/lastLogin")!, method: .put) req.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") req.setValue(authToken, forHTTPHeaderField: "Authorization") let (_, _) = try await URLSession.shared.data(for: req) } func takesVerify(firstName: String, lastName: String, dob: String) async throws -> Bool { guard let authToken = AuthService.shared.authToken else { return false } guard let user = user else { return false } let urlString = "\(SERVER_URL)api/users/\(user.id)/verify" var request = URLRequest(url: URL(string: urlString)!) request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue(authToken, forHTTPHeaderField: "Authorization") request.httpMethod = "PUT" struct SetVerifyBody: Encodable { let firstName: String let lastName: String let dob: String } request.httpBody = try JSONEncoder().encode(SetVerifyBody(firstName: firstName, lastName: lastName, dob: dob)) let (data, response): (Data, URLResponse) = try await URLSession.shared.data(for: request) print("RESPONSE: \(response)") struct VerifyResponse: Codable { var success: Bool } do { let decoded = try JSONDecoder().decode(VerifyResponse.self, from: data) return decoded.success } catch { throw error } } func checkPhoneNumber(phone: String) async throws -> Bool { guard let authToken = AuthService.shared.authToken else { return false } var request = URLRequest(url: URL(string: "\(SERVER_URL)api/users/checkPhone")!) request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" request.setValue(authToken, forHTTPHeaderField: "Authorization") struct PhoneRequestBody: Encodable { let phoneNumber: String } request.httpBody = try JSONEncoder().encode(PhoneRequestBody(phoneNumber: phone)) let (data, response): (Data, URLResponse) = try await URLSession.shared.data(for: request) print("RESPONSE: \(response)") struct CheckPhoneResponse: Decodable { let success: Bool } do { let decoded = try JSONDecoder().decode(CheckPhoneResponse.self, from: data) return decoded.success } catch { print("ERROR HERE: \(error)") throw error } } func needsVerification() async throws -> Bool { guard let userID = self.user?.id else { return true } guard let authToken = AuthService.shared.authToken else { return true } var request = URLRequest(url: URL(string: "\(SERVER_URL)api/users/\(userID)/checkVerification")!) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.setValue(authToken, forHTTPHeaderField: "Authorization") request.httpMethod = "GET" struct Response: Codable { var needsVerification: Bool } let (data, _) = try await URLSession.shared.data(for: request) do { let decoder = JSONDecoder() let decoded = try decoder.decode(Response.self, from: data) return decoded.needsVerification } catch { throw error } } }