My Cloudflare App worker seems to be conflicting with a users custom worker. Basically when they have their worker installed, and install my app their site does not respond at all.
I'm not exactly sure what could be causing it. My worker isn't modifying the request at all. Theirs is but I don't see anything obvious.
I posted his worker here with his permission.
Their worker:
let securityHeaders = {
"Content-Security-Policy" : "upgrade-insecure-requests",
"Strict-Transport-Security" : "max-age=1000",
"X-Xss-Protection" : "1; mode=block",
"X-Frame-Options" : "DENY",
"X-Content-Type-Options" : "nosniff",
"Referrer-Policy" : "strict-origin-when-cross-origin",
}
let sanitiseHeaders = {
"Plan" : "Dental",
"Batman" : "Scientist",
"Lisa-Needs" : "Braces"
}
let removeHeaders = [
"x-goog-generation",
"x-goog-hash",
"x-goog-metageneration",
"x-goog-meta-goog-reserved-file-mtime",
"x-goog-stored-content-encoding",
"x-goog-storage-class",
"x-goog-stored-content-length",
"X-GUploader-UploadID"
]
addEventListener('fetch', event => {
event.respondWith(addHeaders(event.request))
})
async function addHeaders(req) {
let response = await fetch(req)
let newHdrs = new Headers(response.headers)
if (newHdrs.has("Content-Type") && !newHdrs.get("Content-Type").includes("text/html")) {
return new Response(response.body , {
status: response.status,
statusText: response.statusText,
headers: newHdrs
})
}
Object.keys(securityHeaders).map(function(name, index) {
newHdrs.set(name, securityHeaders[name]);
})
Object.keys(sanitiseHeaders).map(function(name, index) {
newHdrs.set(name, sanitiseHeaders[name]);
})
removeHeaders.forEach(function(name){
newHdrs.delete(name)
})
return new Response(response.body , {
status: response.status,
statusText: response.statusText,
headers: newHdrs
})
}
My worker (installed via my Cloudflare App):
function buildLogEntry(request, response) {
const options = INSTALL_OPTIONS
const logDefs = {
rMeth: request.method,
rUrl: request.url,
uAgent: request.headers.get("user-agent"),
cfRay: request.headers.get("cf-ray"),
cIP: request.headers.get("cf-connecting-ip"),
statusCode: response.status,
contentLength: response.headers.get("content-legth"),
cfCacheStatus: response.headers.get("cf-cache-status"),
contentType: response.headers.get("content-type"),
responseConnection: response.headers.get("connection"),
requestConnection: request.headers.get("connection"),
cacheControl: response.headers.get("cache-control"),
acceptRanges: response.headers.get("accept-ranges"),
expectCt: response.headers.get("expect-ct"),
expires: response.headers.get("expires"),
lastModified: response.headers.get("last-modified"),
vary: response.headers.get("vary"),
server: response.headers.get("server"),
etag: response.headers.get("etag"),
date: response.headers.get("date"),
transferEncoding: response.headers.get("transfer-encoding"),
}
const logArray = []
options.metadata.forEach(entry => {
logArray.push(logDefs[entry.field])
})
const logEntry = logArray.join(" | ")
return logEntry
}
async function handleRequest(event) {
const { request } = event
const response = await fetch(request)
const rHost = request.headers.get("host")
const options = INSTALL_OPTIONS
const sourceKey = options.source
const apiKey = options.logflare.api_key
const logEntry = buildLogEntry(request, response)
const init = {
method: "POST",
headers: {
"X-API-KEY": apiKey,
"Content-Type": "application/json",
"User-Agent": `Cloudflare Worker via ${rHost}`,
},
body: JSON.stringify({ source: sourceKey, log_entry: logEntry }),
}
event.waitUntil(fetch("https://logflare.app/api/logs", init))
// console.log(cIP)
return response
}
addEventListener("fetch", event => {
event.respondWith(handleRequest(event))
})
This sounds likely to be a bug deeper in the system, not your fault. Can you e-mail me (kenton at cloudflare) and we'll try to track down what's going on here?
Related
I have been trying to use the Spotify API in my expo app but every tutorial or wrapper I find doesn't seem to work.
I would specifically like to access the 30-second song previews and track/song searching features.
If anyone could provide some guidance or point me towards a working demo of any kind that would be awesome.
Thanks!
Found parts of the solution in https://docs.expo.dev/guides/authentication/#spotify
const discovery = {
authorizationEndpoint: 'https://accounts.spotify.com/authorize',
tokenEndpoint: 'https://accounts.spotify.com/api/token',
};
var client_id = ''; // Your client id
var client_secret = ''; // Your secret
export default function spotifyLogin(props) {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: '',
scopes: ['user-read-email', 'user-read-playback-state', 'playlist-modify-public','playlist-modify-private','playlist-modify-public','playlist-read-private','user-read-recently-played'],
// In order to follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
// this must be set to false
usePKCE: false,
redirectUri: makeRedirectUri({
//scheme: 'your.app'
}),
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
//save code to local storage
props.saveLogin(code)
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
export const getFirstTokenData = async (code) => {
var dataToSend = {
code: code,
redirect_uri: makeRedirectUri(),
grant_type: 'authorization_code'};
//making data to send on server
var formBody = [];
for (var key in dataToSend) {
var encodedKey = encodeURIComponent(key);
var encodedValue = encodeURIComponent(dataToSend[key]);
formBody.push(encodedKey + '=' + encodedValue);
}
formBody = formBody.join('&');
//POST request
var response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST', //Request Type
body: formBody, //post body
headers: {
//Header Defination
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')),
},
})
try{
return await response.json()
}catch (error){
console.log(error)
}
}
export const getRefreshTokenData = async (refreshToken) => {
console.log(refreshToken)
console.log(refreshToken + " going in for refresh")
var dataToSend = {
refresh_token : refreshToken,
grant_type: 'refresh_token'};
//making data to send on server
var formBody = [];
for (var key in dataToSend) {
var encodedKey = encodeURIComponent(key);
var encodedValue = encodeURIComponent(dataToSend[key]);
formBody.push(encodedKey + '=' + encodedValue);
}
formBody = formBody.join('&');
//POST request
var response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST', //Request Type
body: formBody, //post body
headers: {
//Header Defination
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')),
},
})
try{
return await response.json()
}catch (error){
console.log(error)
}
}
The above takes care of auth and getting refresh tokens, below takes care of searching for a track. To get 30 second previews there is a preview property in the return data for getTrack()
const apiPrefix = 'https://api.spotify.com/v1';
export default async ({
offset,
limit,
q,
token,
}) => {
const uri = `${apiPrefix}/search?type=track&limit=${limit}&offset=${offset}&q=${encodeURIComponent(q)}`;
console.log('search begin, uri =', uri, 'token =', token);
const res = await fetch(uri, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
}
});
const json = await res.json();
//console.log('search got json', json);
if (!res.ok) {
return [];
}
return json
// const {
// tracks: {
// items,
// }
// } = json;
// // const items = json.tracks.items;
// return items.map(item => ({
// id: item.id,
// title: item.name,
// imageUri: item.album.images
// ? item.album.images[0].url
// : undefined
// }));
console.log('search end');
};
export const getTrack = async(trackID, token) => {
const uri = `${apiPrefix}/tracks/${trackID}?market=ES`;
const res = await fetch(uri, {
method: 'GET',
headers: {
// Accept: `application/json`,
// Content-Type: `application/json`,
Authorization: `Bearer ${token}`,
}
});
const json = await res.json();
//console.log('search got json', json);
if (!res.ok) {
return [];
}
return json
}
Once upon a time, I worked on a similar application as a test. It's a bit outdated, but I believe Spotify has not changed its API much in the meantime.
Hope this caa help
https://github.com/kubanac95/spotify-test
I know some questions about the subject has been opened here and there, but my issue is different :
all the other ones appear in dev mode, in my case it's in production,
a very big percentage of requests pass, a few of them is TypeError: Network request failed - but sometimes for critical requests
it's random, not always the same request. Sometimes it passes, sometimes not.
it appears to three on my projects, one is on AWS the other one on Clever-Cloud, both are projects between 1000 and 5000 users, servers are quite too big for what they do - I think I removed the risk of a server fault. Even if... I can reproduce locally when I don't start the api locally. So it's like the api is not responding, but as I said, I don't think so.
I have no clue where to dig anymore...
I can give you my API.js service file, maybe you'll find what's wrong ?
import URI from 'urijs';
import { Platform } from 'react-native';
import NetInfo from '#react-native-community/netinfo';
import { getUserToken, wipeData } from '../utils/data';
import { SCHEME, MW_API_HOST } from '../config';
import deviceInfoModule from 'react-native-device-info';
import { capture } from '../utils/sentry';
const unauthorisedHandler = (navigation) => {
wipeData();
navigation.reset({ index: 0, routes: [{ name: 'Auth' }] });
};
const checkNetwork = async (test = false) => {
const isConnected = await NetInfo.fetch().then((state) => state.isConnected);
if (!isConnected || test) {
await new Promise((res) => setTimeout(res, 1500));
return false;
}
return true;
};
class ApiService {
host = MW_API_HOST;
scheme = SCHEME;
getUrl = (path, query) => {
return new URI().host(this.host).scheme(this.scheme).path(path).setSearch(query).toString();
};
execute = async ({ method = 'GET', path = '', query = {}, headers = {}, body = null }) => {
try {
const config = {
method,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
appversion: deviceInfoModule.getBuildNumber(),
appdevice: Platform.OS,
currentroute: this.navigation?.getCurrentRoute?.()?.name,
...headers,
},
body: body ? JSON.stringify(body) : null,
};
const url = this.getUrl(path, query);
console.log('url: ', url);
const canFetch = await checkNetwork();
if (!canFetch) return;
let response;
// To try to avoid mysterious `TypeError: Network request failed` error
// that throws an error directly
// we try catch and try one more time.
try {
response = await fetch(url, config);
} catch (e) {
if (e?.toString().includes('Network request failed')) {
// try again
await new Promise((res) => setTimeout(res, 250));
console.log('try again because Network request failed');
response = await fetch(url, config);
} else {
throw e;
}
}
if (!response.ok) {
if (response.status === 401) {
const token = await getUserToken();
if (token) unauthorisedHandler(API.navigation);
return response;
}
}
if (response.json) return await response.json();
return response;
} catch (e) {
capture(e, { extra: { method, path, query, headers, body } });
return { ok: false, error: "Sorry, an error occured, technical team has been warned." };
}
};
executeWithToken = async ({ method = 'GET', path = '', query = {}, headers = {}, body = null }) => {
const token = await getUserToken();
if (token) headers.Authorization = token;
return this.execute({ method, path, query, headers, body });
};
get = async (args) => this.executeWithToken({ method: 'GET', ...args });
post = async (args) => this.executeWithToken({ method: 'POST', ...args });
put = async (args) => this.executeWithToken({ method: 'PUT', ...args });
delete = async (args) => this.executeWithToken({ method: 'DELETE', ...args });
}
const API = new ApiService();
export default API;
Talking with experts here and there, it seems that it's normal : internet network is not 100% reliable, so sometimes, request fail, for a reason that we can't anticipate (tunnel, whatever).
I ended up using fetch-retry and I still have a few of those, but much less !
I'm trying to verify a HMAC signature received from a WebHook. The details of the WebHook are https://cloudconvert.com/api/v2/webhooks#webhooks-events
This says that the HMAC is generated using hash_hmac (PHP) and is a SHA256 hash of the body - which is JSON. An example received is:
c4faebbfb4e81db293801604d0565cf9701d9e896cae588d73ddfef3671e97d7
This looks like lowercase hexits.
I'm trying to use Cloudflare Workers to process the request, however I can't verify the hash. My code is below:
const encoder = new TextEncoder()
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const contentType = request.headers.get('content-type') || ''
const signature = request.headers.get('CloudConvert-Signature')
let data
await S.put('HEADER', signature)
if (contentType.includes('application/json')) {
data = await request.json()
await S.put('EVENT', data.event)
await S.put('TAG', data.job.tag)
await S.put('JSON', JSON.stringify(data))
}
const key2 = await crypto.subtle.importKey(
'raw',
encoder.encode(CCSigningKey2),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
)
const signed2 = await crypto.subtle.sign(
'HMAC',
key2,
encoder.encode(JSON.stringify(data))
)
await S.put('V22', btoa(String.fromCharCode(...new Uint8Array(signed2))))
return new Response(null, {
status: 204,
headers: {
'Cache-Control': 'no-cache'
}
})
}
This will generate a hash of:
e52613e6ecebdf98bb085f04ca1f91bf9a5cf1dc085f89dcaa3e5fbf5ebf1b06
I've tried use the crypto.subtle.verify method, but that didn't work.
Can anyone see any issues with the code? Or have done this successfully using Cloudflare Workers?
Mark
I finally got this working using the verify method (I had previously tried the verify method, but it didn't work). The main problem seems to the use of request.json() wrapped in JSON.stringify. Changing this to request.text() resolved the issue. I can then use JSON.parse to access the data after verifying the signature. The code is as follows:
const encoder = new TextEncoder()
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const signature = request.headers.get('CloudConvert-Signature')
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(CCSigningKey2),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const data = await request.text()
const verified = await crypto.subtle.verify(
'HMAC',
key,
hexStringToArrayBuffer(signature),
encoder.encode(data)
)
if (!verified) {
return new Response('Verification failed', {
status: 401,
headers: {
'Cache-Control': 'no-cache'
}
})
}
return new Response(null, {
status: 204,
headers: {
'Cache-Control': 'no-cache'
}
})
}
function hexStringToArrayBuffer(hexString) {
hexString = hexString.replace(/^0x/, '')
if (hexString.length % 2 != 0) {
return
}
if (hexString.match(/[G-Z\s]/i)) {
return
}
return new Uint8Array(
hexString.match(/[\dA-F]{2}/gi).map(function(s) {
return parseInt(s, 16)
})
).buffer
}
I am trying to perform a simple post request in React Native with a module that I also use for my website.
I have an api.ts file where the following is defined:
import { ajax } from 'rxjs/observable/dom/ajax';
import { AjaxRequest } from 'rxjs/observable/dom/AjaxObservable';
const ApiClient = {
loginUser: (email: string, password: string) => {
let requestBody = {email, password};
let url = `${dotenv.REACT_APP_API_URL}/api/users/login`;
return createRequestOptions(url, HttpOptions.POST, requestBody);
}
}
The request options method is as follows:
const createRequestOptions = (url: string, method: string, requestBody?: object) => {
let requestOptions: AjaxRequest = {
method: method,
url: url,
crossDomain: true,
responseType: 'json',
headers: {
'Content-Type': 'application/json',
}
};
if ((method === HttpOptions.POST || method === HttpOptions.PUT) && requestBody) {
requestOptions.body = requestBody;
}
console.log(requestOptions);
return ajax(requestOptions);
};
The output of the requestOptions is as follows:
Object {
"body": Object {
"email": "myemail#gmail.com",
"password": "mypassword",
},
"crossDomain": true,
"headers": Object {
"Content-Type": "application/json",
},
"method": "POST",
"responseType": "json",
"url": "http://localhost:3001/api/users/login",
}
Finally in my epic I have the following:
const authLoginEpic: Epic = (action$, store) =>
action$.pipe(
ofType(ActionTypes.AUTH_LOGIN_REQUEST),
mergeMap((action: AuthLoginRequest) =>
ApiClient.loginUser(action.payload.username, action.payload.password).pipe(
map((res: any) => {
return AuthLoginReceive.create({response: res.response, email: action.payload.username});
}),
catchError((err) => {
console.log(JSON.stringify(err));
For some reason the catchError is triggered and I have no idea why this may be. The output of the log is:
{"message":"ajax error","name":"AjaxError","xhr":{"UNSENT":0,"OPENED":1,"HEADERS_RECEIVED":2,"LOADING":3,"DONE":4,"readyState":4,"status":0,"timeout":0,"withCredentials":false,"upload":{},"_aborted":false,"_hasError":true,"_method":"POST","_response":"","_url":"http://localhost:3001/api/users/login","_timedOut":false,"_trackingName":"unknown","_incrementalEvents":false,"_requestId":null,"_cachedResponse":null,"_headers":{"content-type":"application/json"},"_responseType":"json","_sent":true,"_lowerCaseResponseHeaders":{},"_subscriptions":[]},"request":{"async":true,"crossDomain":true,"withCredentials":false,"headers":{"Content-Type":"application/json"},"method":"POST","responseType":"json","timeout":0,"url":"http://localhost:3001/api/users/login","body":"{\"email\":\"mymail#gmail.com\",\"password\":\"mypassword\"}"},"status":0,"responseType":"json","response":null}
The Ajax error is not very descriptive. Does anyone what I may be doing wrong?
It seems that this happened due to the simple fact that the api address was set to localhost or 127.0.0.1
Ensure to have set this to your local network IP address!
I am trying to obtain the access token for Yelp's API.
Yelp's API documentation:
https://www.yelp.com/developers/documentation/v3/get_started
I keep running into the error below on my terminal:
problem with request: getaddrinfo ENOTFOUND api.yelp.com/oauth2/token api.yelp.com/oauth2/token:80
Here's my NodeJS code (I took a lot of it from the Node Documentation site):
var http = require("http");
var postData = JSON.stringify({
"grant_type": "client_credentials",
"client_id": "<<client id>>",
"client_secret": "<<client secret no.>>"
});
var options = {
hostname: 'api.yelp.com/oauth2/token',
port: 80,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
var req = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf8');
res.on('data', (chunk) => {
console.log(`BODY: ${chunk}`);
});
res.on('end', () => {
console.log('No more data in response.');
});
});
req.on('error', (e) => {
console.log(`problem with request: ${e.message}`);
});
// write data to request body
req.write(postData);
req.end();
Two items that caught my eye:
Separate the hostname and the path in your options variable.
The YELP Fusion API is HTTPS, not HTTP. Using HTTP may result in a 302 response (URL Redirection).
var https = require('https');
getYelpAccessCode(function(response) {
var responseData = JSON.parse(response);
if (responseData != null) {
var accessCode = responseData.token_type + " " + responseData.access_token;
}
});
function getYelpAccessCode(callback) {
const postData = querystring.stringify({
'client_id': YELP_CLIENT_ID,
'client_secret': YELP_CLIENT_SECRET
});
const options = {
hostname: 'api.yelp.com',
path: '/oauth2/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = https.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
var body = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
body += chunk;
console.log(`BODY: ${chunk}`);
});
res.on('end', () => {
console.log('No more data in response.');
callback(body);
});
});
req.on('error', (e) => {
console.error(`problem with request: ${e.message}`);
});
// write data to request body
req.write(postData);
req.end();
}