Send Firebase Swift Push using NSURLSession HTTP Post - swift

I set up my app so that users when they login subscribe to a topic with their user UID. Whenever a user sends a message to another user I will be calling the function below that I am hoping to trigger the push.
func sendPushNotification(toUser: String, message: String) {
let urlString = "https://fcm.googleapis.com/fcm/send"
let topic = "\topic\\(toUser)"
let url = NSURL(string: urlString)!
let paramString = "to=\(topic)"
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)!
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
do {
if let jsonData = data {
if let jsonDataDict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.AllowFragments) as? [String: AnyObject] {
NSLog("Received data:\n\(jsonDataDict))")
}
}
} catch let err as NSError {
print(err.debugDescription)
}
}
task.resume()
}
Based on Firebase docs I am supposed to do this HTTP request:
Send to a single topic:
https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
"to": "/topics/foo-bar",
"data": {
"message": "This is a Firebase Cloud Messaging Topic Message!",
}
}
I am struggling to see what I pass in the paramString for the message to be sent based off of their example. Also, where do I define the Authorization:key=

Sending a message with Firebase Cloud Messaging requires that you provide your server key in the Authorization header. As the name suggests, this key should only be used in code that runs on an app server (or other infrastructure that you control). Putting the server key into the app that you ship to your users, means malicious users can use it to send messages on your behalf.
From the Firebase documentation on sending messages to topics (emphasis mine):
From the server side, sending messages to a Firebase Cloud Messaging topic is very similar to sending messages to an individual device or to a user group. The app server sets the to key with a value like /topics/yourTopic.

Related

How can I pass data to my iOS app from a firebase cloud function without making a regular function call request?

I have a Firebase Cloud Function that I call from a URL rather than a function. The URL is used to load a WKWebView and the function is being called using one of the parameters in the URL, specifically the return_url.
An example of the URL to load the WKWebView would be https://domain.name?app_name=app_name&return_url=cloud_function_url.
private func loadWKWebView() {
let url = "https://domain.name"
let params = "param1=param1&return_url=\(cloud_function_url)"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.httpBody = params.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { (data : Data?, response : URLResponse?, error : Error?) in
if data != nil {
if let returnString = String(data: data!, encoding: .utf8) {
DispatchQueue.main.async {
self.webView.loadHTMLString(returnString, baseURL: URL(string: url)!)
}
}
}
}
task.resume()
}
The URL loads an authentication page in which the user must enter their username and password and returns the parameter I need.
I can console log the parameter but I don’t know how to pass the data to my iOS application because it is not a function “directly” that is making the call to the function expecting the result. The result depends on whether the user enters a valid username and password.
How can I send the response once the user logs in to my app?
What you are putting in params is query parameters. That should be appended to the URL, not sent as data in a POST.
I suggest using a URLComponents struct to compose your URL from the parts you need (probably host plus queryItems.)

Authenticating to a GCP HTTP Cloud Function with a Firebase ID Token doesn't work?

I know this is a duplicate question, but I haven't seen anyone present a swift and python implementation. In addition, i've tried everything listed in the other questions, and nothing seems to work
Environments and Conditions:
Calling the http Cloud Function from an iOS app after I get the ID
token (with the ID token)
Written in Swift
The Cloud Function is written in Python
I cannot use HTTP callables as they are not deployable via the current terraform infrastructure in place (at least not that I know of, but would be open to any ideas)
Problem:
So, I was under the assumption that including the Firebase ID token of a Firebase user inside of the Authorization header works ,but it hasn't been for me even with a force refresh. I get a 403 status response with message: The access token could not be verified. That being said, if I go into CLI and get the id token of my actual gcp user account via: gcloud auth print-identity-token then replace the header with said token, I am verified.
Swift Request Code (excuse the lack of convention, this is just POCing before I make a real implementation):
guard let user = Auth.auth(app: authAppToUse).currentUser else { fatalError("SearchMySQL -> user: \(String(describing: Auth.auth(app: authAppToUse).currentUser))") }
user.getIDTokenForcingRefresh(true) { (idToken, err) in
if err != nil{
fatalError("SearchMySQL -> user.getIDToken -> err: \(String(describing: err))")
}
guard let guardedIdToken = idToken else { fatalError("SearchMySQL -> guardedIdToken: \(String(describing: idToken))") }
let rawJSON = [
"data" : [
"table": table,
"search_by": searchBy,
"search_by_value": searchByValue
]
]
guard let guardedJSON = try? JSONSerialization.data(withJSONObject: rawJSON, options: .prettyPrinted) else {
return
}
guard let url = URL(string: "https://us-east1-fresh-customer-dev-ocean.cloudfunctions.net/mysql_search") else { return }
var request = URLRequest(url: url)
request.addValue("Bearer \(idToken)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = guardedJSON
let task = URLSession.shared.dataTask(with: request) { (data, response, err) in
if err != nil {
print(err?.localizedDescription)
fatalError("SearchMySQL -> URLSession.shared.dataTask -> err: \(String(describing: err))")
}
guard let guardedData = data else {
return
}
print(idToken)
print(response.debugDescription)
let json = try? JSONSerialization.jsonObject(with: guardedData, options: .allowFragments)
print(json)
completion(data)
}
task.resume()
}
Python Cloud Function:
def main(request):
"""Background Http Triggered Cloud Function for ********.
Validates authentication with Firebase ID token
Returns:
********.main(payload): http response code and data payload (or message)
"""
# validate request authorization
if not request.headers.get('Authorization'):
log.fatal('No Authorization Token provided')
return {'message': 'No Authorization Token provided'}, 400
try:
id_token = request.headers.get('Authorization')
auth.verify_id_token(id_token)
except (ValueError, InvalidIdTokenError, ExpiredIdTokenError, RevokedIdTokenError, CertificateFetchError) as e:
log.fatal(f'Authorization `id_token` error: {e}')
return {'message':f'Authorization `id_token` error: {e}'}, 400
data_payload = request.get_json(force=True)
if not data_payload.table:
log.fatal('Payload missing table field.')
return {'message': 'Payload missing table field'}, 422
if not data_payload.search_by:
log.fatal('Payload missing search_by field.')
return {'message': 'Payload missing search_by field'}, 422
return ********.main(data_payload)
Ideas/Questions:
Aren't Firebase ID tokens equivalent to Google ID Tokens?
Could there be an iAM permission issue for the (auto generated)
firebase service account?
Do I need to also add any of the plist values to the idtoken when
sending it over in the header?
Could it be something with the rules of my cloud function?
Am I missing something, or is this intended/expected behavior? With
http callables being regular http functions but with protocols
packaged to facilitate, I would think that this is a relatively easy
implementation....
I've thought of the route of using an admin function to send a
message to the mobile instance that needs a google id token during
login, but the overhead and latency would result in issues.

Saving CoreData to a Web Server with Swift 3.0

This question is related to: Swift Core Data Sync With Web Server.
I have followed the steps that have been mentioned in the question above however I am unable to apply the third step to my current project.
I currently have a class called Records
class Records {
static let shared = Records()
var records = [Record]()
let context = PersistenceServce.context
let request = NSFetchRequest<Record>(entityName: "Record")
func recordData() -> [Record] {
do {
records = try context.fetch(Record.fetchRequest())
}catch {
print("Error fetching data from CoreData")
}
return records
}
}
and here is how I display the data on my tableViewController.
func getData() {
records = Records.shared.recordData()
self.tableView.reloadData()
}
I do know how save data to a web server as this tutorial explains: https://www.simplifiedios.net/swift-php-mysql-tutorial/ as well as check for internet connection. However I am unsure how to apply it to the CoreData where there are multiple data involved.
If anyone could direct me to a solution or an explain how this can be achieved I'd very much appreciate it.
The question that you have linked is not trying to explain how to communicate with a web server. It is explaining how to store data in core data and tag/mark it in a way that you know which records have been sent to the web server or not.
So the Predicate will fetch all records that have not been sent to the web server and allow you to send them when you have an internet connection available.
Communicating with a web server can be a broad topic and will depend on your web server and API setup, so it is too much to explain here fully. I refer you to some free online resources that will help you understand networking in Swift.
Udacity - Networking with Swift
Ray Wenderlich - Alamofire Tutorial
Stack Overflow - Swift POST Request
Here is an example of a POST Request from the StackOverflow answer above
var request = URLRequest(url: URL(string: "http://test.tranzporthub.com/street45/customer_login.php")!)
request.httpMethod = "POST"
let postString = "user_id=chaitanya3191#gmail.com&password=123"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
}
task.resume()
Using code similar to this, you should be able to send data to your web server, then your web server can do whatever it likes with it.
UPDATE:
To encode your parameters to JSON you can use the following code as a guide
var dictionary = [
"username": "Test User",
"password": "Password"
]
if let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: []) {
// jsonData is a byte sequence, to view it you would need to convert to string
print(String(bytes: jsonData, encoding: String.Encoding.utf8))
}
Which would output:
Optional("{\"username\":\"Test User\",\"password\":\"Password\"}")
Note: you would send it as data, not the string version. so your code might look like this:
request.httpBody = jsonData

LinkedIn login with Stormpath

I am able to get the access token of my LinkedIn account in swift using the LinkedIn SDK. How do I authenticate this login with Stormpath?
[Update]
let APIURL = "https://api.stormpath.com/v1/applications/LI_APPLICATION_ID/accounts"
func sendRequestWithJSON(accessToken:String)
{
let json = [ "providerData" : ["providerId": "linkedin", "accessToken": accessToken] ]
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(json, options: .PrettyPrinted)
let username = STORMPATH_API_KEY_ID
let password = STORMPATH_API_KEY_SECRET
let loginString = NSString(format: "%#:%#", username, password)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedDataWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
// create post request
let url = NSURL(string: APIURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
// insert json data to the request
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.HTTPBody = jsonData
let task = NSURLSession.sharedSession().dataTaskWithRequest(request){ data, response, error in
if error != nil{
print("Error -> \(error)")
return
}
do {
let result = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String:AnyObject]
print("Result -> \(result)")
} catch {
print("Error -> \(error)")
}
}
task.resume()
//return task
} catch {
print(error)
}
}
I am passing accessToken fetch from linkedin to above function, but it return below result:
["message": Authentication required., "status": 401, "code": 401, "developerMessage": Authentication with a valid API Key is required., "moreInfo": http://www.stormpath.com/docs/quickstart/connect]
What's wrong I am doing?
LinkedIn is an interesting beast, since their mobile SDKs have two flaws:
An end user NEEDS the LinkedIn app to be installed, otherwise the "login" button will redirect the user to the App Store.
The mobile access token cannot be used on the server. See this screenshot from LinkedIn's iOS documentation
So - to get auth working on mobile, I would recommend using a server to handle the flow, so you don't have to worry about those two downsides. This is roughly:
The app will redirect the user to your webserver.
The webserver begins the LinkedIn authentication flow, and redirects the user to LinkedIn.
The user logs into LinkedIn, and gets redirected back to your webserver.
The webserver reads the response, and exchanges the Authorization Code with LinkedIn for an access token.
The webserver redirects your user back to the app, using a custom url scheme to send it the LinkedIn access token.
The app uses the LinkedIn access token to login to Stormpath.
Sound complicated? It's actually more straightforward than it seems. I actually wrote some demo code for this flow using Express.js & Swift if you want to try it out. Let me know if it works for you!

Quickbooks create a TimeActivity

How can I make a POST request to create a time activity for my company on Quickbooks from a mobile app? I've got authorisation working fine, now I just need to know how to create items. For the HTTPBody of the request, what should I enter?
let url = NSURL(string:”Some Fancy URL“)
let request = NSMutableURLRequest(URL: url!)
var err: NSError?
var bodyData = “myBodyKey=myBodyValue“ as NSString
request.HTTPMethod = "POST"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding)!
This is (a way) of setting the HTTPBody of a request
In regards to Quickbooks, they have a api documentation regarding the time activity.
They explain here how to send a JSON Create Request.
And use as example
{
"TxnDate":"2013-01-28",
"NameOf":"Vendor",
"VendorRef":{
"value":"61"
},
"CustomerRef":{
"value":"60"
},
"DepartmentRef":{
"value":"3"
},
"ItemRef":{
"value":"4"
},
"ClassRef":{
"value":"100100000000000321202"
},
"BillableStatus":"Billable",
"Taxable":true,
"HourlyRate":251,
"BreakHours":1,
"BreakMinutes":0,
"StartTime":"2013-01-28T08:00:00-08:00",
"EndTime":"2013-01-28T17:00:00-08:00",
"Description":"Single activity time sheet",
"domain":"QBO",
"sparse":false
}
As A data you would need to create a dictionary and encode this as json.