How can i send receipt data using Swift to Node.js server - javascript

I'm currently trying to verify IAP receipts of user subscriptions in iOS. At first i make a post request that gets me the receipt data from the receipt currently stored in Bundle.main.appStoreReceiptURL. I'm able to get use this receipt data in my server to verify the receipt which works pretty well. However this receipt data string is hard coded in Node which is specific to a certain user. How can i be able to send this receipt string to my server for any receipt depending on any user to be verified. Here is both my Swift and server code. Apple says in the documentation
To retrieve the receipt data, use the appStoreReceiptURL method of NSBundle to locate the app’s receipt, and then read the entire file. Send this data to your server—as with all interactions with your server, the details are your responsibility.
I would like to get the receipt data and send it to my server.
func getReceipt() {
if let receiptUrl = receiptUrl {
do {
let purchaseReceipt = try Data(contentsOf: receiptUrl)
self.validatePurchaseReceipt(pReceiptData: purchaseReceipt)
} catch {
let receiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
receiptRefreshRequest.delegate = self
receiptRefreshRequest.start()
}
}
}
func validatePurchaseReceipt(pReceiptData: Data) {
let base64encodedReceipt = pReceiptData.base64EncodedString()
let secretKey = "myAppstoreConnectSecretKey"
let requestReceiptDict = ["receipt-data": base64encodedReceipt, "password": secretKey]
guard JSONSerialization.isValidJSONObject(requestReceiptDict) else { return }
do {
let data = try JSONSerialization.data(withJSONObject: requestReceiptDict)
let validationString = "https://sandbox.itunes.apple.com/verifyReceipt"
guard let validationUrl = URL(string: validationString) else { return }
let session = URLSession(configuration: .default)
var request = URLRequest(url: validationUrl, cachePolicy: .reloadIgnoringLocalCacheData)
request.httpMethod = "POST"
let task = session.uploadTask(with:request, from: data) { (data, response, error) in
guard let data = data, error == nil else { return }
do {
let purchaseReceiptJSON = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print("Success retrieved json:\(purchaseReceiptJSON)")
} catch let error {
print(error)
}
}
task.resume()
} catch let error {
print(error)
}
}
This is my server code
const express = require('express');
const requirePromise = require('request-promise');
const app = express();
let verificationURL = 'https://sandbox.itunes.apple.com/verifyReceipt';
let secretKey = 'myAppstoreConnectSecretKey';
let receiptData = 'MIIntgYJKoZIhvcNAQcCoIInpzCCJ6MCAQExCzAJBgUrDgMCGgUAMIIXVwYJKoZIhvcNAQcBoIIXSASCFetc'
const options = {
url: verificationURL,
method: 'POST',
headers: {
'User-Agent': 'Request-Promise',
'Content-Type': 'application/x-www-form-urlencoded',
},
json: true
};
options.form = JSON.stringify({
'receipt-data': receiptData,
'password': secretKey
});
requirePromise(options).then((resData) => {
console.log(resData);
return resData;
}).catch(function (error) {
console.log(error);
});

If you're going to be using your server to validate receipts (which you should) then the validatePurchaseReceipt method should be calling your server, not the /verifyReceipt endpoint. The receipt data is passed to your server exactly like you do with requestReceiptDict.
Also, the secretKey should be hardcoded on your server - not anywhere on the client.
Here's a similar question: https://stackoverflow.com/a/54261816/3166209

Related

What is the proper way to send a json body with axios from React to Django Rest Api

I am trying to send a post request to my django rest API using Axios from a react frontend.
Home.js
Here I am getting the value from the form and setting up the state and sending the value to the function that will call the API on submit
const [query, setQuery] = useState({
search: "",
})
const { search } = query;
// function handle submit
const onSubmit = (e) => {
e.preventDefault();
console.log(search);
navigate(`/search/${search}`);
fetchSearch(search); //fucntion that deals with the api call
};
searchApi.js
this function deals with api call
export const fetchSearch = async (search) => {
console.log("the data being recieved ", search);
const config = {
header: {
"Content-Type": "application/json",
},
};
const body = JSON.stringify({ search }); // 1
const body = { // 2
search: search,
};
console.log("this is the body", body);
try {
let response = await axios.post(`${url}/get-teacher/`, body, config);
console.log(response.data);
} catch (error) {
console.log(error);
}
};
Now let me tell you the problem I am facing, as shown above, I have comments beside the body. If I send the body //1 as a request, then django throws this error and I get an internal server error
AssertionError: Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'NoneType'>`
And if I send the body //2 as a request then it works perfectly.
the view that handles the request
#api_view(['POST'])
def search(request):
query = request.data.get('search')
print("this is the query recieved" ,query)
if query is not None:
try:
user = TeacherDetail.objects.filter(is_verified= True)
# to chain lookups to query multiple fields at once
lookups = Q(name__icontains = query) | Q(location__icontains = query) | Q(full_address__icontains = query) | Q(name_of_school__icontains = query) | Q(experience__icontains = query) | Q(teach_for_no_days__icontains = query) | Q(subject__icontains = query) | Q(shift__icontains = query) | Q(teach_class__icontains = query) | Q(board__icontains = query)
user = user.filter(lookups)
serializer = TeacherDetailSerializer(user, many = True)
return Response(serializer.data)
except Exception as e:
e = str(e)
return Response({'status':status.HTTP_404_NOT_FOUND, "message": e})
else:
response = {
"message": "Query is empty",
"status": status.HTTP_404_NOT_FOUND
}
I want to know why this is happening because when I console.log() the body //1 is see
{"search":"swastika"}
which is exactly what we send to the body of the request.
Please correct me where I am wrong and tell me the proper way to send the body as JSON

Bad Request when trying to get an access token

I'm trying to use the Nest Device API, however I am unable to get an Access Token as it throws
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
I've downloaded my credentials.json from GCP and I open a new tab with the AUTH_URL below:
const credentials = require('../../../credentials.json');
const PROJECT_ID = <NEST DEVICE API PROJECT ID>;
const REDIRECT_URL = 'http://localhost:3000/connect/callback';
const AUTH_URL =
`https://nestservices.google.com/partnerconnections/${PROJECT_ID}/auth?` +
`redirect_uri=${REDIRECT_URL}&access_type=offline&prompt=consent&client_id=${credentials.web.client_id}&` +
`response_type=code&scope=https://www.googleapis.com/auth/sdm.service`;
From that, I have my callback page that gets the authcode.
const credentials = require('../../../credentials.json');
const { code } = router.query; // Auth Code
try {
const url =
`https://www.googleapis.com/oauth2/v4/token?client_id=${credentials.web.client_id}` +
`&client_secret=${credentials.web.client_secret}&code=${code}&grant_type=authorization_code&` +
`redirect_uri=${REDIRECT_URL}`;
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
});
console.log(response);
} catch (e) {
console.error(e);
}
This is where the API returns the above error. I have also tried taking this URL and doing curl -L -X POST <url> however I get exactly the same results.
Any ideas?

How to cache a simple javascript array in Cloudflare and return it to Slack?

I am trying to create a Slack bot that will add a developer to a release queue using a CloudFlare worker. Example: /releasenext <your name>, Slack bot will add you to the queue and will tell you who else is in that queue.
I would like to use Cloudflare cache to cache names coming from the POST request. So I am using a javascript array. I am trying to cache that array, and anytime somebody is adding their name to the queue, the Slack bot should reveal the persons already in the queue and the new person who added their name.
So far, I managed to create the communication between Cloudflare and Slack via a command on Slack /releasenext <your name>, and Slack answers me with: the next person to release is: <your name>.
I found the documentation and some examples but I am not understanding how it works exactly.
here is the documentation: https://workers.cloudflare.com/docs/reference/workers-concepts/using-cache/
And here is my code:
const SLACK_TOKEN = "secret"
const BOT_NAME = "Deploy-bot 🤖"
let jsonHeaders = new Headers([["Content-Type", "application/json"]])
let resp
addEventListener('fetch', event => {
event.respondWith(slackWebhookHandler(event.request))
resp = fetch(event.request, { cf: {cacheEverything: true} })
resp = fetch(event.request, { cf: { cacheTtl: 300 } })
})
// simpleResponse generates a simple JSON response
// with the given status code and message.
function simpleResponse(statusCode, message) {
let resp = {
message: message,
status: statusCode
}
return new Response(JSON.stringify(resp), {
headers: jsonHeaders,
status: statusCode
})
}
// slackResponse builds a message for Slack with the given text
// #param {string} text - the message text to return
function slackResponse(text) {
let content = {
response_type: "in_channel",
text: text
}
try {
return new Response(JSON.stringify(content), {
headers: jsonHeaders,
status: 200
})
} catch (e) {
return simpleResponse(
200,
"Sorry, I had an issue generating a response. Try again in a bit!"
)
}
}
// parseMessage parses the selected name from the Slack message.
// #return {string} - the name.
function parseMessage(message) {
try {
const name = message.get("text").trim()
return {name: name}
} catch (e) {
return null
}
}
async function addPersonToQueue(name) {
try {
let cachedResponse = false
if (resp.headers.get("cf-cache-status").toLowerCase() === "hit") {
cachedResponse = true
}
const nameQueue = []
nameQueue.push({name: name}, {cached: cachedResponse})
let output = nameQueue.map(item => {return item.name}).join(', ')
return output
} catch (e) {
throw new Error(`could not fetch the selected name: ${e}`)
}
}
// slackWebhookHandler handles an incoming Slack webhook and generates a response.
// #param {Request} request
async function slackWebhookHandler(request) {
if (request.method !== "POST") {
return simpleResponse(
200,
`Hi, I'm ${BOT_NAME}, a Slack bot for fetching the latest person name to release`
)
}
let formData
try {
formData = await request.formData()
if (formData.get("token").toString() !== SLACK_TOKEN) {
return simpleResponse(403, "Invalid Slack verification token")
}
} catch (e) {
return simpleResponse(400, "could not decode POST form data")
}
try {
let parsed = parseMessage(formData)
if (parsed === null) {
throw new Error("could not parse your message")
}
let reply = await addPersonToQueue(parsed.name)
return slackResponse(
`The next person to release is: *${reply}*`,
`the cache is: ${reply.cached}`
)
} catch (e) {
return simpleResponse(
200,
`Sorry, I had an issue retrieving names from the release queue ${e}`
)
}
}
The error message I have now is
{"message":"Sorry, I had an issue retrieving names from the release queue Error: could not fetch the selected name: TypeError: Cannot read property 'get' of undefined","status":200}
The expected output is: The next person to release is: person1, <your name>.
Assuming that another person already added their name to the queue first.

Acquiring a new token with `axios.interceptors`

When the token expires, I want to get a new token based on refresh_token. I have read that this can be obtained with axios.interceptors.
Please check if:
Have I correctly configured axios.interceptors?
Have I placed it in the right place, i.e. above theItems class.
axios.interceptors.response is assigned to theinterceptor variable. What should I do with this variable?
In addition to `axios.interceptors', I need to get a new token. The token is valid for 24 hours.
Do I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?
Where should I put 'client_id', 'secret_id', 'grant_type'?
Code here: https://stackblitz.com/edit/react-pkea41
import axios from 'axios';
axios.defaults.baseURL = localStorage.getItem('domain');
const interceptor = axios.interceptors.response.use(
response => response,
error => {
// Reject promise if usual error
if (errorResponse.status !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Eject the interceptor so it doesn't loop in case
* token refresh causes the 401 response
*/
axios.interceptors.response.eject(interceptor);
return axios.post('/api/refresh_token', {
'refresh_token': JSON.parse(localStorage.getItem('token'))['refresh_token']
}).then(response => {
/*saveToken();*/
localStorage.setItem('token', JSON.stringify(response.data));
error.response.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
return axios(error.response.config);
}).catch(error => {
/*destroyToken();*/
localStorage.setItem('token', '');
this.router.push('/login');
return Promise.reject(error);
}).finally(createAxiosResponseInterceptor);
}
);
class Items extends Component {
constructor (props) {
super(props);
this.state = {
}
}
render () {
return (
<div >
</div>
)
}
}
render(<Items />, document.getElementById('root'));
This is what I did before. Your configuration is a little different from mine.
const baseURL = localStorage.getItem('domain');
const defaultOptions = {
baseURL,
method: 'get',
headers: {
'Content-Type': 'application/json',
}
};
// Create Instance
const axiosInstance = axios.create(defaultOptions);
// Get token from session
const accessToken = ...
// Set the auth token for any request
instance.interceptors.request.use(config => {
config.headers.Authorization = accessToken ? `Bearer ${accessToken}` : '';
return config;
});
// Last step: handle request error general case
instance.interceptors.response.use(
response => response,
error => {
// Error
const { config, response: { status } } = error;
if (status === 401) {
// Unauthorized request: maybe access token has expired!
return refreshAccessToken(config);
} else {
return Promise.reject(error);
}
}
});
I think this part should be separated with Components - it will be placed on helpers or utils.
Also, you have to wait for 24 hrs because refreshToken() method is never called before 24 hrs.
You don't need to process client_id, secret_id, grant_type right here.
Please check if I have correctly configured axios.interceptors.
I think it works. But I suggest that you should test it carefully.This is a good article to refer https://blog.liplex.de/axios-interceptor-to-refresh-jwt-token-after-expiration/
Have I placed it in the right place, i.e. above theItems class. ?
You should create a service function to wrap Axios and API configs,and interceptor of course
axios.interceptors.response is assigned to the interceptor variable. What should I do with this variable?
It is just a variable used to define the interceptor. Don't care about it. If you want to avoid assigning it, just do it inside a function like this Automating access token refreshing via interceptors in axios
I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?
You can change the token saved in your localStorage, and do that
Where should I put 'client_id', 'secret_id', 'grant_type'?
If you store it inside localStorage, it's accessible by any script inside your page (which is as bad as it sounds as an XSS attack can let an external attacker get access to the token).
Don't store it in local storage (or session storage). If any of the 3rd part scripts you include in your page gets compromised, it can access all your users' tokens.
The JWT needs to be stored inside an HttpOnly cookie, a special kind of cookie that's only sent in HTTP requests to the server, and it's never accessible (both for reading or writing) from JavaScript running in the browser.
Please check if I have correctly configured axios.interceptors.
From what I can see the configuration seems ok, as it's the same of this answer https://stackoverflow.com/a/53294310/4229159
Have I placed it in the right place, i.e. above theItems class. ?
That is something that I can't answer, every application is different, it's not the best place to put it, but might be OK for an example. In your app however it should be together with all the API calls (for example)
axios.interceptors.response is assigned to theinterceptor variable. What should I do with this variable?
As you can see, the variable that got answered from the call to /refresh_token for assigned to config.headers['Authorization'] = 'Bearer ' + response.data.access_token; if you backend reads from there the auth value you should be fine
I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?
You should wait unless the backend can change that, and expire the token in less time (EG in 5 or 2 minutes)
Where should I put 'client_id', 'secret_id', 'grant_type'?
Seems like the backend should have that, unless they are public ones... You are probably the best to know whether that belongs to the config for the call or if you are authenticating with them. If you are authenticating with them and they are the ones that grant you a token, then you shouldn't put it in the client side, as it is a security risk
1) Configuration looks fine to me. But your solution won't work when there are multiple parallel requests and all of them trying to refresh auth token at the same time. Believe me this is a issue is really hard to pin point. So better be covered upfront.
2) No. Not the right place. Create a separate service (I call it api.service) and do all the network/api commutation using that.
3) There is no use of interceptor variable. You can avoid assigning it to a variable.
4) If have control over the API you can reduce the timeout for a bit. Also i think 24 hours is bit too long. Else no option I guess.
5) Not sure you have to deal with them.
Bellow is a working code of api.service.ts. You might have to change few things here and there to fit that in to your application. If you get the concept clearly it wont be hard. Also it cover multiple parallel request problem as well.
import * as queryString from 'query-string';
import axios, { AxiosRequestConfig, Method } from 'axios';
import { accountService } from '../account.service'; //I use account service to authentication related services
import { storageService } from './storage.service'; //I use storage service to keep the auth token. inside it it uses local storage to save values
var instance = axios.create({
baseURL: 'your api base url goes here',
});
axios.defaults.headers.common['Content-Type'] = 'application/json';
export const apiService = {
get,
post,
put,
patch,
delete: deleteRecord,
delete2: deleteRecord2
}
function get<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('get', controller, action, null, urlParams, queryParams);
}
function post<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('post', controller, action, data, urlParams, queryParams);
}
function put<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('put', controller, action, data, urlParams, queryParams);
}
function patch<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('patch', controller, action, data, urlParams, queryParams);
}
function deleteRecord(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<any>('delete', controller, action, null, urlParams, queryParams);
}
function deleteRecord2<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('delete', controller, action, null, urlParams, queryParams);
}
function apiRequest<T>(method: Method, controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
var url = createUrl(controller, action, urlParams, queryParams);
var options = createRequestOptions(url, method, data);
return instance.request<T>(options)
.then(res => res && res.data)
.catch(error => {
if (error.response) {
//handle error appropriately: if you want to display a descriptive error notification this is the place
} else {
//handle error appropriately: if you want to display a a generic error message
}
throw error;
});
}
function createUrl(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
let url = controller + (action ? '/' + action : '');
urlParams.forEach(param => {
url += '/' + param;
});
let params = '';
if (queryParams) {
params += '?' + queryString.stringify(queryParams);
}
return url += params;
}
function createRequestOptions(url: string, method: Method, data: any, responseType?: any) {
var authToken = storageService.getAuthToken();
var jwtToken = authToken != null ? authToken.authToken : '';
var options: AxiosRequestConfig = {
url,
method,
data,
headers: {
'Authorization': 'bearer ' + jwtToken
},
}
if (responseType) {
options.responseType = responseType;
}
return options;
}
let isRefreshing = false;
let failedQueue: any[] = [];
const processQueue = (error: any, token: string = '') => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
}
instance.interceptors.response.use(undefined, (error) => {
const originalRequest = error.config;
if (originalRequest && error.response && error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function (resolve, reject) {
failedQueue.push({ resolve, reject })
}).then(authToken => {
originalRequest.headers.Authorization = 'bearer ' + authToken;
return axios(originalRequest);
}).catch(err => {
return err;
})
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise(function (resolve, reject) {
accountService.refreshToken()
.then(result => {
if (result.succeeded) {
originalRequest.headers.Authorization = 'bearer ' + result.authToken;
axios(originalRequest).then(resolve, reject);
processQueue(null, result.authToken);
} else {
reject(error);
}
}).catch((err) => {
processQueue(err);
reject(err);
}).then(() => { isRefreshing = false });
});
}
return Promise.reject(error);
});
Cheers,

get access token from URL to execute an API on submit with React

this is the url of the page where i want to get the access token from and set it to execute the api on submit :
http://localhost:3001/reset-password?access_token=RLMVvTsmcxB5g9laDn6TENhx7ggFzG5ammKlGrMHoqAsuc0P1PA7z0mZgtGcISMq
this is the handlesubmit of this page :
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
console.log("values", values);
//this.loginUser(values);
if (!err) {
this.props.showAuthLoader();
this.props.userReset(values);
}
});
};
this is the service where I am executing the api
const resetPassword = async (newPassword) => {
const user = localStorage.user;
const accessToken = user ? JSON.parse(user).id : '';
const wsResponse = await axios.request({
method: 'post',
// url: 'http://localhost:3000/api/users/reset',
url: `${endpointReset.USER_Reset}?access_token=${accessToken}`,
data: {
newPassword,
}
});
return wsResponse;
}
When I execute it now I am getting 401 authorization required and that's obvious because the api is executed without access token.
I am using loopback for backend on port 3000 and react on port 3001. Any help would be appreciated. Thank you

Categories

Resources