mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-25 18:07:31 +09:00
Dactyloidae iOS initial commit
This commit is contained in:
parent
daa6179d22
commit
7154a0497e
2123 changed files with 197052 additions and 0 deletions
562
mobile/ios/Account/FxAClient10.swift
Normal file
562
mobile/ios/Account/FxAClient10.swift
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Alamofire
|
||||
import Shared
|
||||
import Foundation
|
||||
import FxA
|
||||
import Deferred
|
||||
import SwiftyJSON
|
||||
|
||||
public let FxAClientErrorDomain = "org.mozilla.fxa.error"
|
||||
public let FxAClientUnknownError = NSError(domain: FxAClientErrorDomain, code: 999,
|
||||
userInfo: [NSLocalizedDescriptionKey: "Invalid server response"])
|
||||
|
||||
let KeyLength: Int = 32
|
||||
|
||||
public struct FxALoginResponse {
|
||||
public let remoteEmail: String
|
||||
public let uid: String
|
||||
public let verified: Bool
|
||||
public let sessionToken: Data
|
||||
public let keyFetchToken: Data
|
||||
}
|
||||
|
||||
public struct FxAccountRemoteError {
|
||||
static let AttemptToOperateOnAnUnverifiedAccount: Int32 = 104
|
||||
static let InvalidAuthenticationToken: Int32 = 110
|
||||
static let EndpointIsNoLongerSupported: Int32 = 116
|
||||
static let IncorrectLoginMethodForThisAccount: Int32 = 117
|
||||
static let IncorrectKeyRetrievalMethodForThisAccount: Int32 = 118
|
||||
static let IncorrectAPIVersionForThisAccount: Int32 = 119
|
||||
static let UnknownDevice: Int32 = 123
|
||||
static let DeviceSessionConflict: Int32 = 124
|
||||
static let UnknownError: Int32 = 999
|
||||
}
|
||||
|
||||
public struct FxAKeysResponse {
|
||||
let kA: Data
|
||||
let wrapkB: Data
|
||||
}
|
||||
|
||||
public struct FxASignResponse {
|
||||
let certificate: String
|
||||
}
|
||||
|
||||
public struct FxAStatusResponse {
|
||||
let exists: Bool
|
||||
}
|
||||
|
||||
public struct FxADevicesResponse {
|
||||
let devices: [FxADevice]
|
||||
}
|
||||
|
||||
public struct FxANotifyResponse {
|
||||
let success: Bool
|
||||
}
|
||||
|
||||
public struct FxAOAuthResponse {
|
||||
let accessToken: String
|
||||
}
|
||||
|
||||
public struct FxAProfileResponse {
|
||||
let email: String
|
||||
let uid: String
|
||||
let avatarURL: String?
|
||||
let displayName: String?
|
||||
}
|
||||
|
||||
public struct FxADeviceDestroyResponse {
|
||||
let success: Bool
|
||||
}
|
||||
|
||||
// fxa-auth-server produces error details like:
|
||||
// {
|
||||
// "code": 400, // matches the HTTP status code
|
||||
// "errno": 107, // stable application-level error number
|
||||
// "error": "Bad Request", // string description of the error type
|
||||
// "message": "the value of salt is not allowed to be undefined",
|
||||
// "info": "https://docs.dev.lcip.og/errors/1234" // link to more info on the error
|
||||
// }
|
||||
|
||||
public enum FxAClientError {
|
||||
case remote(RemoteError)
|
||||
case local(NSError)
|
||||
}
|
||||
|
||||
// Be aware that string interpolation doesn't work: rdar://17318018, much good that it will do.
|
||||
extension FxAClientError: MaybeErrorType {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .remote(error):
|
||||
let errorString = error.error ?? NSLocalizedString("Missing error", comment: "Error for a missing remote error number")
|
||||
let messageString = error.message ?? NSLocalizedString("Missing message", comment: "Error for a missing remote error message")
|
||||
return "<FxAClientError.Remote \(error.code)/\(error.errno): \(errorString) (\(messageString))>"
|
||||
case let .local(error):
|
||||
return "<FxAClientError.Local Error Domain=\(error.domain) Code=\(error.code) \"\(error.localizedDescription)\">"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemoteError {
|
||||
let code: Int32
|
||||
let errno: Int32
|
||||
let error: String?
|
||||
let message: String?
|
||||
let info: String?
|
||||
|
||||
var isUpgradeRequired: Bool {
|
||||
return errno == FxAccountRemoteError.EndpointIsNoLongerSupported
|
||||
|| errno == FxAccountRemoteError.IncorrectLoginMethodForThisAccount
|
||||
|| errno == FxAccountRemoteError.IncorrectKeyRetrievalMethodForThisAccount
|
||||
|| errno == FxAccountRemoteError.IncorrectAPIVersionForThisAccount
|
||||
}
|
||||
|
||||
var isInvalidAuthentication: Bool {
|
||||
return code == 401
|
||||
}
|
||||
|
||||
var isUnverified: Bool {
|
||||
return errno == FxAccountRemoteError.AttemptToOperateOnAnUnverifiedAccount
|
||||
}
|
||||
}
|
||||
|
||||
open class FxAClient10 {
|
||||
let authURL: URL
|
||||
let oauthURL: URL
|
||||
let profileURL: URL
|
||||
|
||||
public init(authEndpoint: URL? = nil, oauthEndpoint: URL? = nil, profileEndpoint: URL? = nil) {
|
||||
self.authURL = authEndpoint ?? ProductionFirefoxAccountConfiguration().authEndpointURL as URL
|
||||
self.oauthURL = oauthEndpoint ?? ProductionFirefoxAccountConfiguration().oauthEndpointURL as URL
|
||||
self.profileURL = profileEndpoint ?? ProductionFirefoxAccountConfiguration().profileEndpointURL as URL
|
||||
}
|
||||
|
||||
open class func KW(_ kw: String) -> Data {
|
||||
return ("identity.mozilla.com/picl/v1/" + kw).utf8EncodedData
|
||||
}
|
||||
|
||||
/**
|
||||
* The token server accepts an X-Client-State header, which is the
|
||||
* lowercase-hex-encoded first 16 bytes of the SHA-256 hash of the
|
||||
* bytes of kB.
|
||||
*/
|
||||
open class func computeClientState(_ kB: Data) -> String? {
|
||||
if kB.count != 32 {
|
||||
return nil
|
||||
}
|
||||
return kB.sha256.subdata(in: 0..<16).hexEncodedString
|
||||
}
|
||||
|
||||
open class func quickStretchPW(_ email: Data, password: Data) -> Data {
|
||||
var salt = KW("quickStretch")
|
||||
salt.append(":".utf8EncodedData)
|
||||
salt.append(email)
|
||||
return (password as NSData).derivePBKDF2HMACSHA256Key(withSalt: salt as Data!, iterations: 1000, length: 32)
|
||||
}
|
||||
|
||||
open class func computeUnwrapKey(_ stretchedPW: Data) -> Data {
|
||||
let salt: Data = Data()
|
||||
let contextInfo: Data = KW("unwrapBkey")
|
||||
let bytes = (stretchedPW as NSData).deriveHKDFSHA256Key(withSalt: salt, contextInfo: contextInfo, length: UInt(KeyLength))
|
||||
return bytes!
|
||||
}
|
||||
|
||||
fileprivate class func remoteError(fromJSON json: JSON, statusCode: Int) -> RemoteError? {
|
||||
if json.error != nil || 200 <= statusCode && statusCode <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let code = json["code"].int32 {
|
||||
if let errno = json["errno"].int32 {
|
||||
return RemoteError(code: code, errno: errno,
|
||||
error: json["error"].string,
|
||||
message: json["message"].string,
|
||||
info: json["info"].string)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate class func loginResponse(fromJSON json: JSON) -> FxALoginResponse? {
|
||||
guard json.error == nil,
|
||||
let uid = json["uid"].string,
|
||||
let verified = json["verified"].bool,
|
||||
let sessionToken = json["sessionToken"].string,
|
||||
let keyFetchToken = json["keyFetchToken"].string else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return FxALoginResponse(remoteEmail: "", uid: uid, verified: verified,
|
||||
sessionToken: sessionToken.hexDecodedData, keyFetchToken: keyFetchToken.hexDecodedData)
|
||||
}
|
||||
|
||||
fileprivate class func keysResponse(fromJSON keyRequestKey: Data, json: JSON) -> FxAKeysResponse? {
|
||||
guard json.error == nil,
|
||||
let bundle = json["bundle"].string else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let data = bundle.hexDecodedData
|
||||
guard data.count == 3 * KeyLength else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let ciphertext = data.subdata(in: 0..<(2 * KeyLength))
|
||||
let MAC = data.subdata(in: (2 * KeyLength)..<(3 * KeyLength))
|
||||
|
||||
let salt: Data = Data()
|
||||
let contextInfo: Data = KW("account/keys")
|
||||
let bytes = (keyRequestKey as NSData).deriveHKDFSHA256Key(withSalt: salt, contextInfo: contextInfo, length: UInt(3 * KeyLength))
|
||||
let respHMACKey = bytes?.subdata(in: 0..<KeyLength)
|
||||
let respXORKey = bytes?.subdata(in: KeyLength..<(3 * KeyLength))
|
||||
|
||||
guard let hmacKey = respHMACKey,
|
||||
ciphertext.hmacSha256WithKey(hmacKey) == MAC else {
|
||||
NSLog("Bad HMAC in /keys response!")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let xorKey = respXORKey,
|
||||
let xoredBytes = ciphertext.xoredWith(xorKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let kA = xoredBytes.subdata(in: 0..<KeyLength)
|
||||
let wrapkB = xoredBytes.subdata(in: KeyLength..<(2 * KeyLength))
|
||||
return FxAKeysResponse(kA: kA, wrapkB: wrapkB)
|
||||
}
|
||||
|
||||
fileprivate class func signResponse(fromJSON json: JSON) -> FxASignResponse? {
|
||||
guard json.error == nil,
|
||||
let cert = json["cert"].string else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return FxASignResponse(certificate: cert)
|
||||
}
|
||||
|
||||
fileprivate class func statusResponse(fromJSON json: JSON) -> FxAStatusResponse? {
|
||||
guard json.error == nil,
|
||||
let exists = json["exists"].bool else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return FxAStatusResponse(exists: exists)
|
||||
}
|
||||
|
||||
fileprivate class func devicesResponse(fromJSON json: JSON) -> FxADevicesResponse? {
|
||||
guard json.error == nil,
|
||||
let jsonDevices = json.array else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let devices = jsonDevices.flatMap { (jsonDevice) -> FxADevice? in
|
||||
return FxADevice.fromJSON(jsonDevice)
|
||||
}
|
||||
|
||||
return FxADevicesResponse(devices: devices)
|
||||
}
|
||||
|
||||
fileprivate class func notifyResponse(fromJSON json: JSON) -> FxANotifyResponse {
|
||||
return FxANotifyResponse(success: json.error == nil)
|
||||
}
|
||||
|
||||
fileprivate class func deviceDestroyResponse(fromJSON json: JSON) -> FxADeviceDestroyResponse {
|
||||
return FxADeviceDestroyResponse(success: json.error == nil)
|
||||
}
|
||||
|
||||
fileprivate class func oauthResponse(fromJSON json: JSON) -> FxAOAuthResponse? {
|
||||
guard json.error == nil,
|
||||
let accessToken = json["access_token"].string else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return FxAOAuthResponse(accessToken: accessToken)
|
||||
}
|
||||
|
||||
fileprivate class func profileResponse(fromJSON json: JSON) -> FxAProfileResponse? {
|
||||
guard json.error == nil,
|
||||
let uid = json["uid"].string,
|
||||
let email = json["email"].string else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let avatarURL = json["avatar"].string
|
||||
let displayName = json["displayName"].string
|
||||
|
||||
return FxAProfileResponse(email: email, uid: uid, avatarURL: avatarURL, displayName: displayName)
|
||||
}
|
||||
|
||||
lazy fileprivate var alamofire: SessionManager = {
|
||||
let ua = UserAgent.fxaUserAgent
|
||||
let configuration = URLSessionConfiguration.ephemeral
|
||||
var defaultHeaders = SessionManager.default.session.configuration.httpAdditionalHeaders ?? [:]
|
||||
defaultHeaders["User-Agent"] = ua
|
||||
configuration.httpAdditionalHeaders = defaultHeaders
|
||||
return SessionManager(configuration: configuration)
|
||||
}()
|
||||
|
||||
open func login(_ emailUTF8: Data, quickStretchedPW: Data, getKeys: Bool) -> Deferred<Maybe<FxALoginResponse>> {
|
||||
let authPW = (quickStretchedPW as NSData).deriveHKDFSHA256Key(withSalt: Data(), contextInfo: FxAClient10.KW("authPW"), length: 32) as NSData
|
||||
|
||||
let parameters = [
|
||||
"email": NSString(data: emailUTF8, encoding: String.Encoding.utf8.rawValue)!,
|
||||
"authPW": authPW.base16EncodedString(options: NSDataBase16EncodingOptions.lowerCase) as NSString,
|
||||
]
|
||||
|
||||
var URL: URL = self.authURL.appendingPathComponent("/account/login")
|
||||
if getKeys {
|
||||
var components = URLComponents(url: URL, resolvingAgainstBaseURL: false)!
|
||||
components.query = "keys=true"
|
||||
URL = components.url!
|
||||
}
|
||||
var mutableURLRequest = URLRequest(url: URL)
|
||||
mutableURLRequest.httpMethod = HTTPMethod.post.rawValue
|
||||
|
||||
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
mutableURLRequest.httpBody = JSON(parameters).stringValue()?.utf8EncodedData
|
||||
|
||||
return makeRequest(mutableURLRequest, responseHandler: FxAClient10.loginResponse)
|
||||
}
|
||||
|
||||
open func status(forUID uid: String) -> Deferred<Maybe<FxAStatusResponse>> {
|
||||
let statusURL = self.authURL.appendingPathComponent("/account/status").withQueryParam("uid", value: uid)
|
||||
var mutableURLRequest = URLRequest(url: statusURL)
|
||||
mutableURLRequest.httpMethod = HTTPMethod.get.rawValue
|
||||
|
||||
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
return makeRequest(mutableURLRequest, responseHandler: FxAClient10.statusResponse)
|
||||
}
|
||||
|
||||
open func devices(withSessionToken sessionToken: NSData) -> Deferred<Maybe<FxADevicesResponse>> {
|
||||
let URL = self.authURL.appendingPathComponent("/account/devices")
|
||||
var mutableURLRequest = URLRequest(url: URL)
|
||||
mutableURLRequest.httpMethod = HTTPMethod.get.rawValue
|
||||
|
||||
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let salt: Data = Data()
|
||||
let contextInfo: Data = FxAClient10.KW("sessionToken")
|
||||
let key = sessionToken.deriveHKDFSHA256Key(withSalt: salt, contextInfo: contextInfo, length: UInt(2 * KeyLength))!
|
||||
mutableURLRequest.addAuthorizationHeader(forHKDFSHA256Key: key)
|
||||
|
||||
return makeRequest(mutableURLRequest, responseHandler: FxAClient10.devicesResponse)
|
||||
}
|
||||
|
||||
open func notify(deviceIDs: [GUID], collectionsChanged collections: [String], reason: String, withSessionToken sessionToken: NSData) -> Deferred<Maybe<FxANotifyResponse>> {
|
||||
let httpBody = JSON([
|
||||
"to": deviceIDs,
|
||||
"payload": [
|
||||
"version": 1,
|
||||
"command": "sync:collection_changed",
|
||||
"data": [
|
||||
"collections": collections,
|
||||
"reason": reason
|
||||
]
|
||||
]
|
||||
])
|
||||
return self.notify(httpBody: httpBody, withSessionToken: sessionToken)
|
||||
}
|
||||
|
||||
open func notifyAll(ownDeviceId: GUID, collectionsChanged collections: [String], reason: String, withSessionToken sessionToken: NSData) -> Deferred<Maybe<FxANotifyResponse>> {
|
||||
let httpBody = JSON([
|
||||
"to": "all",
|
||||
"excluded": [ownDeviceId],
|
||||
"payload": [
|
||||
"version": 1,
|
||||
"command": "sync:collection_changed",
|
||||
"data": [
|
||||
"collections": collections,
|
||||
"reason": reason
|
||||
]
|
||||
]
|
||||
])
|
||||
return self.notify(httpBody: httpBody, withSessionToken: sessionToken)
|
||||
}
|
||||
|
||||
fileprivate func notify(httpBody: JSON, withSessionToken sessionToken: NSData) -> Deferred<Maybe<FxANotifyResponse>> {
|
||||
let URL = self.authURL.appendingPathComponent("/account/devices/notify")
|
||||
var mutableURLRequest = URLRequest(url: URL)
|
||||
mutableURLRequest.httpMethod = HTTPMethod.post.rawValue
|
||||
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
mutableURLRequest.httpBody = httpBody.stringValue()?.utf8EncodedData
|
||||
|
||||
let salt: Data = Data()
|
||||
let contextInfo: Data = FxAClient10.KW("sessionToken")
|
||||
let key = sessionToken.deriveHKDFSHA256Key(withSalt: salt, contextInfo: contextInfo, length: UInt(2 * KeyLength))!
|
||||
mutableURLRequest.addAuthorizationHeader(forHKDFSHA256Key: key)
|
||||
|
||||
return makeRequest(mutableURLRequest, responseHandler: FxAClient10.notifyResponse)
|
||||
}
|
||||
|
||||
open func destroyDevice(ownDeviceId: GUID, withSessionToken sessionToken: NSData) -> Deferred<Maybe<FxADeviceDestroyResponse>> {
|
||||
let URL = self.authURL.appendingPathComponent("/account/device/destroy")
|
||||
var mutableURLRequest = URLRequest(url: URL)
|
||||
let httpBody: JSON = JSON(["id": ownDeviceId])
|
||||
mutableURLRequest.httpMethod = HTTPMethod.post.rawValue
|
||||
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
mutableURLRequest.httpBody = httpBody.stringValue()?.utf8EncodedData
|
||||
|
||||
let salt: Data = Data()
|
||||
let contextInfo: Data = FxAClient10.KW("sessionToken")
|
||||
let key = sessionToken.deriveHKDFSHA256Key(withSalt: salt, contextInfo: contextInfo, length: UInt(2 * KeyLength))!
|
||||
mutableURLRequest.addAuthorizationHeader(forHKDFSHA256Key: key)
|
||||
|
||||
return makeRequest(mutableURLRequest, responseHandler: FxAClient10.deviceDestroyResponse)
|
||||
}
|
||||
|
||||
open func registerOrUpdate(device: FxADevice, withSessionToken sessionToken: NSData) -> Deferred<Maybe<FxADevice>> {
|
||||
let URL = self.authURL.appendingPathComponent("/account/device")
|
||||
var mutableURLRequest = URLRequest(url: URL)
|
||||
mutableURLRequest.httpMethod = HTTPMethod.post.rawValue
|
||||
|
||||
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
mutableURLRequest.httpBody = device.toJSON().stringValue()?.utf8EncodedData
|
||||
|
||||
let salt: Data = Data()
|
||||
let contextInfo: Data = FxAClient10.KW("sessionToken")
|
||||
let key = sessionToken.deriveHKDFSHA256Key(withSalt: salt, contextInfo: contextInfo, length: UInt(2 * KeyLength))!
|
||||
mutableURLRequest.addAuthorizationHeader(forHKDFSHA256Key: key)
|
||||
|
||||
return makeRequest(mutableURLRequest, responseHandler: FxADevice.fromJSON)
|
||||
}
|
||||
|
||||
open func oauthAuthorize(withSessionToken sessionToken: NSData, keyPair: RSAKeyPair, certificate: String) -> Deferred<Maybe<FxAOAuthResponse>> {
|
||||
let audience = self.getAudience(forURL: self.oauthURL)
|
||||
|
||||
let assertion = JSONWebTokenUtils.createAssertionWithPrivateKeyToSign(with: keyPair.privateKey,
|
||||
certificate: certificate,
|
||||
audience: audience)
|
||||
|
||||
let oauthAuthorizationURL = self.oauthURL.appendingPathComponent("/authorization")
|
||||
var mutableURLRequest = URLRequest(url: oauthAuthorizationURL)
|
||||
mutableURLRequest.httpMethod = HTTPMethod.post.rawValue
|
||||
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let parameters = [
|
||||
"assertion": assertion,
|
||||
"client_id": AppConstants.FxAiOSClientId,
|
||||
"response_type": "token",
|
||||
"scope": "profile",
|
||||
"ttl": "300"
|
||||
]
|
||||
|
||||
let salt: Data = Data()
|
||||
let contextInfo: Data = FxAClient10.KW("sessionToken")
|
||||
let key = sessionToken.deriveHKDFSHA256Key(withSalt: salt, contextInfo: contextInfo, length: UInt(2 * KeyLength))!
|
||||
|
||||
guard let httpBody = JSON(parameters as NSDictionary).stringValue()?.utf8EncodedData else {
|
||||
return deferMaybe(FxAClientError.local(FxAClientUnknownError))
|
||||
}
|
||||
|
||||
mutableURLRequest.httpBody = httpBody
|
||||
mutableURLRequest.addAuthorizationHeader(forHKDFSHA256Key: key)
|
||||
|
||||
return makeRequest(mutableURLRequest, responseHandler: FxAClient10.oauthResponse)
|
||||
}
|
||||
|
||||
open func getProfile(withSessionToken sessionToken: NSData) -> Deferred<Maybe<FxAProfileResponse>> {
|
||||
let keyPair = RSAKeyPair.generate(withModulusSize: 1024)!
|
||||
return self.sign(sessionToken as Data, publicKey: keyPair.publicKey) >>== { signResult in
|
||||
return self.oauthAuthorize(withSessionToken: sessionToken, keyPair: keyPair, certificate: signResult.certificate) >>== { oauthResult in
|
||||
|
||||
let profileURL = self.profileURL.appendingPathComponent("/profile")
|
||||
var mutableURLRequest = URLRequest(url: profileURL)
|
||||
mutableURLRequest.httpMethod = HTTPMethod.get.rawValue
|
||||
|
||||
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
mutableURLRequest.setValue("Bearer " + oauthResult.accessToken, forHTTPHeaderField: "Authorization")
|
||||
|
||||
return self.makeRequest(mutableURLRequest, responseHandler: FxAClient10.profileResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func getAudience(forURL URL: URL) -> String {
|
||||
if let port = URL.port {
|
||||
return "\(URL.scheme!)://\(URL.host!):\(port)"
|
||||
} else {
|
||||
return "\(URL.scheme!)://\(URL.host!)"
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func makeRequest<T>(_ request: URLRequest, responseHandler: @escaping (JSON) -> T?) -> Deferred<Maybe<T>> {
|
||||
let deferred = Deferred<Maybe<T>>()
|
||||
|
||||
alamofire.request(request)
|
||||
.validate(contentType: ["application/json"])
|
||||
.responseJSON { response in
|
||||
withExtendedLifetime(self.alamofire) {
|
||||
if let error = response.result.error {
|
||||
deferred.fill(Maybe(failure: FxAClientError.local(error as NSError)))
|
||||
return
|
||||
}
|
||||
|
||||
if let data = response.result.value {
|
||||
let json = JSON(data)
|
||||
if let remoteError = FxAClient10.remoteError(fromJSON: json, statusCode: response.response!.statusCode) {
|
||||
deferred.fill(Maybe(failure: FxAClientError.remote(remoteError)))
|
||||
return
|
||||
}
|
||||
|
||||
if let response = responseHandler(json) {
|
||||
deferred.fill(Maybe(success: response))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
deferred.fill(Maybe(failure: FxAClientError.local(FxAClientUnknownError)))
|
||||
}
|
||||
}
|
||||
|
||||
return deferred
|
||||
}
|
||||
}
|
||||
|
||||
extension FxAClient10: FxALoginClient {
|
||||
|
||||
func keyPair() -> Deferred<Maybe<KeyPair>> {
|
||||
let result = RSAKeyPair.generate(withModulusSize: 2048)! // TODO: debate key size and extract this constant.
|
||||
return Deferred(value: Maybe(success: result))
|
||||
}
|
||||
|
||||
open func keys(_ keyFetchToken: Data) -> Deferred<Maybe<FxAKeysResponse>> {
|
||||
let URL = self.authURL.appendingPathComponent("/account/keys")
|
||||
var mutableURLRequest = URLRequest(url: URL)
|
||||
mutableURLRequest.httpMethod = HTTPMethod.get.rawValue
|
||||
|
||||
let salt: Data = Data()
|
||||
let contextInfo: Data = FxAClient10.KW("keyFetchToken")
|
||||
let key = (keyFetchToken as NSData).deriveHKDFSHA256Key(withSalt: salt, contextInfo: contextInfo, length: UInt(3 * KeyLength))!
|
||||
mutableURLRequest.addAuthorizationHeader(forHKDFSHA256Key: key)
|
||||
|
||||
let rangeStart = 2 * KeyLength
|
||||
let keyRequestKey = key.subdata(in: rangeStart..<(rangeStart + KeyLength))
|
||||
|
||||
return makeRequest(mutableURLRequest) { FxAClient10.keysResponse(fromJSON: keyRequestKey, json: $0) }
|
||||
}
|
||||
|
||||
open func sign(_ sessionToken: Data, publicKey: PublicKey) -> Deferred<Maybe<FxASignResponse>> {
|
||||
let parameters = [
|
||||
"publicKey": publicKey.jsonRepresentation() as NSDictionary,
|
||||
"duration": NSNumber(value: OneDayInMilliseconds), // The maximum the server will allow.
|
||||
]
|
||||
|
||||
let url = self.authURL.appendingPathComponent("/certificate/sign")
|
||||
var mutableURLRequest = URLRequest(url: url)
|
||||
mutableURLRequest.httpMethod = HTTPMethod.post.rawValue
|
||||
|
||||
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
mutableURLRequest.httpBody = JSON(parameters as NSDictionary).stringValue()?.utf8EncodedData
|
||||
|
||||
let salt: Data = Data()
|
||||
let contextInfo: Data = FxAClient10.KW("sessionToken")
|
||||
let key = (sessionToken as NSData).deriveHKDFSHA256Key(withSalt: salt, contextInfo: contextInfo, length: UInt(2 * KeyLength))!
|
||||
mutableURLRequest.addAuthorizationHeader(forHKDFSHA256Key: key)
|
||||
|
||||
return makeRequest(mutableURLRequest, responseHandler: FxAClient10.signResponse)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue