We have an Cloudflare Enterprise plan and want to pass in the regionCode (state abbreviation) into the header response back to the client. The Cloudflare request is req.cf.regionCode but for whatever reason I constantly get an error when I try to assign it to a variable so there is something wrong with the syntax.
Ultimately the header output should be:
state: CA (would show the state abbreviation from the client's IP lookup, in this case it would be California)
addEventListener("fetch", event => {
event.respondWith(fetchAndReplace(event.request))
})
async function fetchAndReplace(request) {
const res = await fetch(request)
let modifiedHeaders = new Headers()
let state = (req.cf.regionCode)
modifiedHeaders.set('Content-Type', 'text/html')
modifiedHeaders.append('Pragma', 'no-cache')
modifiedHeaders.append("state", `${state}`)
//Return maint page if you're not calling from a trusted UA
if (request.headers.get("User-Agent") !== "Preprod")
{
// Return modified response.
return new Response(maintPage, {
status: 503,
headers: modifiedHeaders
})
}
else //Allow users from trusted into site
{
//Fire all other requests directly to our WebServers
return fetch(request)
}
}
let maintPage = `
This is a test
`;
Related
I'm lost trying to understand how Dart shelf executes the middleware and handlers. From all the documentation I have read (and briefing it up) if you write a Middleware that returns null, then the execution goes down the pipeline.
Otherwise if the middleware returns a Response, then the execution down the pipeline is stopped, and the Response is returned to the caller.
I have a server with a simple pipeline like this:
var handler = const shelf.Pipeline()
.addMiddleware(shelf.logRequests())
//.addMiddleware(options)
.addMiddleware(auth)
.addHandler(Router.handle);
The auth middleware checks 3 cases: Register, Login and Verify.
Register -> Creates new user and returns Response.ok(token), or if nor possible Response.InternalServerError
Login -> Refreshes the token and returns Response.ok(token), or if not correct Response(401)
Verify -> Returns null when ok(should continue down the pipeline), or Response(403, forbidden)
The problem is, that I cannot stop the execution of the middlewares. If I make a successful login, still the program goes down the pipeline and calls the Router. Which of course doesn't have the path for register and returns 404 as it is expected to do.
According to shelf documentation, it is supposed to stop when a middleware returns a response. What the hell am I doing wrong?
This is the code of the auth Middleware for reference:
abstract class AuthProvider {
static JsonDecoder _decoder = const JsonDecoder();
static FutureOr<Response> handle(Request request) async {
print('Entering auth middleware');
if(request.url.toString() == 'login'){
print('into login from auth');
AuthProvider.auth(request);
}
else if(request.url.toString() == 'register'){
print('Into register from auth');
RegisterController.handle(request);
}
else {
print('Into verify from auth');
AuthProvider.verify(request);
}
}
static FutureOr<Response> auth(Request request) async {
print('Entering auth');
String sql;
var query = ExecQuery();
try {
dynamic data = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
final user = data['email'].toString();
final hash = Hash.create(data['password'].toString());
sql =
'''SELECT COUNT(*) FROM public.user WHERE (email = '${user}' AND password = '${hash}')''';
await query.countSql(sql);
if (query.result.status && query.result.opResult[0][0] == 1) {
JwtClaim claim = JwtClaim(
subject: user,
issuer: 'Me',
audience: ['users'],
);
final token = issueJwtHS256(claim, config.secret);
sql = '''UPDATE public.user SET token = '${token}'
WHERE (email = '${user}' AND password = '${hash}')''';
await query.rawQuery(sql);
return Response.ok(token);
}
else{throw Exception();}
} catch (e) {
return Response(401, body: 'Incorrect username/password');
}
}
static FutureOr<Response> verify(Request request) async {
print('Entering verify');
try {
final token = request.headers['Authorization'].replaceAll('Bearer ', '');
print('Received token: ${token}');
final claim = verifyJwtHS256Signature(token, config.secret);
print('got the claim');
claim.validate(issuer: 'ACME Widgets Corp',
audience: 'homacenter');
print ('returning null in middleware');
return null;
} catch(e) {
print(e.toString());
return Response.forbidden('Authorization rejected');
}
}
}
I reply myself... after losing days in this, a return was missing, that made the pipeline keep going. Issue closed.
abstract class AuthProvider {
static JsonDecoder _decoder = const JsonDecoder();
static FutureOr<Response> handle(Request request) async {
if(request.url.toString() == 'login'){
return AuthProvider.auth(request);
}
else if(request.url.toString() == 'register'){
return RegisterController.handle(request);
}
else {
return AuthProvider.verify(request);
}
}
I've read here that it's possible to send an IPN directly to a Google cloud function. I have my Google Cloud functions running on Firebase on an index.js file.
I've set up my Paypal buttons to send the IPN to a page on my webapp.
Here is an example of one of the functions I'm running off Google Cloud Functions/Firebase:
// UPDATE ROOMS INS/OUTS
exports.updateRoomIns = functions.database.ref('/doors/{MACaddress}').onWrite((change, context) => {
const beforeData = change.before.val();
const afterData = change.after.val();
const roomPushKey = afterData.inRoom;
const insbefore = beforeData.ins;
const insafter = afterData.ins;
if ((insbefore === null || insbefore === undefined) && (insafter === null || insafter === undefined) || insbefore === insafter) {
return 0;
} else {
const updates = {};
Object.keys(insafter).forEach(key => {
updates['/rooms/' + roomPushKey + '/ins/' + key] = true;
});
return admin.database().ref().update(updates); // do the update}
}
return 0;
});
Now question:
1) I want to add another function to process IPN from Paypal as soon as I have a transaction. How would I go about this?
I'll mark the answer as correct if solves this first question.
2) how would that Google cloud function even look like?
I'll create another question if you can solve this one.
Note I am using Firebase (no other databases nor PHP).
IPN is simply a server that tries to reach a given endpoint.
First, you have to make sure that your firebase plan supports 3rd party requests (it's unavailable in the free plan).
After that, you need to make an http endpoint, like so:
exports.ipn = functions.http.onRequest((req, res) => {
// req and res are instances of req and res of Express.js
// You can validate the request and update your database accordingly.
});
It will be available in https://www.YOUR-FIREBASE-DOMAIN.com/ipn
Based on #Eliya Cohen answer:
on your firebase functions create a function such as:
exports.ipn = functions.https.onRequest((req, res) => {
var reqBody = req.body;
console.log(reqBody);
// do something else with the req.body i.e: updating a firebase node with some of that info
res.sendStatus(200);
});
When you deploy your functions go to your firebase console project and check your functions. You should have something like this:
Copy that url, go to paypal, edit the button that's triggering the purchase, scroll down to Step 3 and at the bottom type:
notify_url= paste that url here
Save changes.
You can now test your button and check the req.body on your firebase cloud functions Log tab.
Thanks to the answers here, and especially to this gist: https://gist.github.com/dsternlicht/fdef0c57f2f2561f2c6c477f81fa348e,
.. finally worked out a solution to verify the IPN request in a cloud func:
let CONFIRM_URL_SANDBOX = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
exports.ipn = functions.https.onRequest((req, res) => {
let body = req.body;
logr.debug('body: ' + StringUtil.toStr(body));
let postreq = 'cmd=_notify-validate';
// Iterate the original request payload object
// and prepend its keys and values to the post string
Object.keys(body).map((key) => {
postreq = `${postreq}&${key}=${body[key]}`;
return key;
});
let request = require('request');
let options = {
method: 'POST',
uri : CONFIRM_URL_SANDBOX,
headers: {
'Content-Length': postreq.length,
},
encoding: 'utf-8',
body: postreq
};
res.sendStatus(200);
return new Promise((resolve, reject) => {
// Make a post request to PayPal
return request(options, (error, response, resBody) => {
if (error || response.statusCode !== 200) {
reject(new Error(error));
return;
}
let bodyResult = resBody.substring(0, 8);
logr.debug('bodyResult: ' + bodyResult);
// Validate the response from PayPal and resolve / reject the promise.
if (resBody.substring(0, 8) === 'VERIFIED') {
return resolve(true);
} else if (resBody.substring(0, 7) === 'INVALID') {
return reject(new Error('IPN Message is invalid.'));
} else {
return reject(new Error('Unexpected response body.'));
}
});
});
});
Also thanks to:
https://developer.paypal.com/docs/classic/ipn/ht-ipn/#do-it
IPN listener request-response flow: https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNImplementation/
To receive IPN message data from PayPal, your listener must follow this request-response flow:
Your listener listens for the HTTPS POST IPN messages that PayPal sends with each event.
After receiving the IPN message from PayPal, your listener returns an empty HTTP 200 response to PayPal. Otherwise, PayPal resends the IPN message.
Your listener sends the complete message back to PayPal using HTTPS POST.
Prefix the returned message with the cmd=_notify-validate variable, but do not change the message fields, the order of the fields, or the character encoding from the original message.
Extremely late to the party but for anyone still looking for this, PayPal have made a sample in their JS folder on their IPN samples Github repo.
You can find this at:
https://github.com/paypal/ipn-code-samples/blob/master/javascript/googlecloudfunctions.js
I work on an MS Teams app with a connection to MS Graph.
I'm trying to get an access token for MS Graph in MS Teams. To get a token I'm using MSAL js.
If I run the App with gulp serve I receive a valid token and I have access to the MS Graph endpoints. But if I build the app and install it in MS Teams the function userAgentApplication.acquireTokenSilent(config) is never executed. I tested it with a console.log before and after the call. There is no error thrown.
Do you have any idea why the above snippet is not executed in MS Teams (app and webapp)?
NEW:
On Home:
export function login() {
const url = window.location.origin + '/login.html';
microsoftTeams.authentication.authenticate({
url: url,
width: 600,
height: 535,
successCallback: function(result: string) {
console.log('Login succeeded: ' + result);
let data = localStorage.getItem(result) || '';
localStorage.removeItem(result);
let tokenResult = JSON.parse(data);
storeToken(tokenResult.accessToken)
},
failureCallback: function(reason) {
console.log('Login failed: ' + reason);
}
});
}
On Login
microsoftTeams.initialize();
// Get the tab context, and use the information to navigate to Azure AD login page
microsoftTeams.getContext(function (context) {
// Generate random state string and store it, so we can verify it in the callback
let state = _guid();
localStorage.setItem("simple.state", state);
localStorage.removeItem("simple.error");
// See https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-implicit
// for documentation on these query parameters
let queryParams = {
client_id: "XXX",
response_type: "id_token token",
response_mode: "fragment",
scope: "https://graph.microsoft.com/User.Read openid",
redirect_uri: window.location.origin + "/login-end.html",
nonce: _guid(),
state: state,
login_hint: context.loginHint,
};
// Go to the AzureAD authorization endpoint (tenant-specific endpoint, not "common")
// For guest users, we want an access token for the tenant we are currently in, not the home tenant of the guest.
let authorizeEndpoint = `https://login.microsoftonline.com/${context.tid}/oauth2/v2.0/authorize?` + toQueryString(queryParams);
window.location.assign(authorizeEndpoint);
});
// Build query string from map of query parameter
function toQueryString(queryParams) {
let encodedQueryParams = [];
for (let key in queryParams) {
encodedQueryParams.push(key + "=" + encodeURIComponent(queryParams[key]));
}
return encodedQueryParams.join("&");
}
// Converts decimal to hex equivalent
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _decimalToHex(number) {
var hex = number.toString(16);
while (hex.length < 2) {
hex = '0' + hex;
}
return hex;
}
// Generates RFC4122 version 4 guid (128 bits)
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _guid() {...}
on login end
microsoftTeams.initialize();
localStorage.removeItem("simple.error");
let hashParams = getHashParameters();
if (hashParams["error"]) {
// Authentication/authorization failed
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure(hashParams["error"]);
} else if (hashParams["access_token"]) {
// Get the stored state parameter and compare with incoming state
let expectedState = localStorage.getItem("simple.state");
if (expectedState !== hashParams["state"]) {
// State does not match, report error
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("StateDoesNotMatch");
} else {
// Success -- return token information to the parent page.
// Use localStorage to avoid passing the token via notifySuccess; instead we send the item key.
let key = "simple.result";
localStorage.setItem(key, JSON.stringify({
idToken: hashParams["id_token"],
accessToken: hashParams["access_token"],
tokenType: hashParams["token_type"],
expiresIn: hashParams["expires_in"]
}));
microsoftTeams.authentication.notifySuccess(key);
}
} else {
// Unexpected condition: hash does not contain error or access_token parameter
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("UnexpectedFailure");
}
// Parse hash parameters into key-value pairs
function getHashParameters() {
let hashParams = {};
location.hash.substr(1).split("&").forEach(function (item) {
let s = item.split("="),
k = s[0],
v = s[1] && decodeURIComponent(s[1]);
hashParams[k] = v;
});
return hashParams;
}
Even though my answer is quite late, but I faced the same problem recently.
The solution is farely simple: MSALs silent login does not work in MSTeams (yet) as MSTeams relies on an IFrame Approach that is not supported by MSAL.
You can read all about it in this Github Issue
Fortunately, they are about to release a fix for this in Version msal 1.2.0 and there is already an npm-installable beta which should make this work:
npm install msal#1.2.0-beta.2
Update: I tried this myself - and the beta does not work as well. This was confirmed by Microsoft in my own Github Issue.
So I guess at the time being, you simply can't use MSAL for MS Teams.
I need to make a POST request and send some data. I'm using the service worker sync to handle offline situation.
But is there a way to pass the POST data to the service worker, so it makes the same request again?
Cause apparently the current solution is to store requests in some client side storage and after client gets connection - get the requests info from the storage and then send them.
Any more elegant way?
PS: I thought about just making the service worker send message to the application code so it does the request again ... but unfortunately it doesn't know the exact client that registered the service worker :(
You can use fetch-sync
or i use postmessage to fix this problem, which i agree that indexedDB looks trouble.
first of all, i send the message from html.
// send message to serviceWorker
function sync (url, options) {
navigator.serviceWorker.controller.postMessage({type: 'sync', url, options})
}
i got this message in serviceworker, and then i store it.
const syncStore = {}
self.addEventListener('message', event => {
if(event.data.type === 'sync') {
// get a unique id to save the data
const id = uuid()
syncStore[id] = event.data
// register a sync and pass the id as tag for it to get the data
self.registration.sync.register(id)
}
console.log(event.data)
})
in the sync event, i got the data and fetch
self.addEventListener('sync', event => {
// get the data by tag
const {url, options} = syncStore[event.tag]
event.waitUntil(fetch(url, options))
})
it works well in my test, what's more you can delete the memory store after the fetch
what's more, you may want to send back the result to the page. i will do this in the same way by postmessage.
as now i have to communicate between each other, i will change the fucnction sync into this way
// use messagechannel to communicate
sendMessageToSw (msg) {
return new Promise((resolve, reject) => {
// Create a Message Channel
const msg_chan = new MessageChannel()
// Handler for recieving message reply from service worker
msg_chan.port1.onmessage = event => {
if(event.data.error) {
reject(event.data.error)
} else {
resolve(event.data)
}
}
navigator.serviceWorker.controller.postMessage(msg, [msg_chan.port2])
})
}
// send message to serviceWorker
// you can see that i add a parse argument
// this is use to tell the serviceworker how to parse our data
function sync (url, options, parse) {
return sendMessageToSw({type: 'sync', url, options, parse})
}
i also have to change the message event, so that i can pass the port to sync event
self.addEventListener('message', event => {
if(isObject(event.data)) {
if(event.data.type === 'sync') {
// in this way, you can decide your tag
const id = event.data.id || uuid()
// pass the port into the memory stor
syncStore[id] = Object.assign({port: event.ports[0]}, event.data)
self.registration.sync.register(id)
}
}
})
up to now, we can handle the sync event
self.addEventListener('sync', event => {
const {url, options, port, parse} = syncStore[event.tag] || {}
// delete the memory
delete syncStore[event.tag]
event.waitUntil(fetch(url, options)
.then(response => {
// clone response because it will fail to parse if it parse again
const copy = response.clone()
if(response.ok) {
// parse it as you like
copy[parse]()
.then(data => {
// when success postmessage back
port.postMessage(data)
})
} else {
port.postMessage({error: response.status})
}
})
.catch(error => {
port.postMessage({error: error.message})
})
)
})
At the end. you cannot use postmessage to send response directly.Because it's illegal.So you need to parse it, such as text, json, blob, etc. i think that's enough.
As you have mention that, you may want to open the window.
i advice that you can use serviceworker to send a notification.
self.addEventListener('push', function (event) {
const title = 'i am a fucking test'
const options = {
body: 'Yay it works.',
}
event.waitUntil(self.registration.showNotification(title, options))
})
self.addEventListener('notificationclick', function (event) {
event.notification.close()
event.waitUntil(
clients.openWindow('https://yoursite.com')
)
})
when the client click we can open the window.
To comunicate with the serviceworker I use a trick:
in the fetch eventlistener I put this:
self.addEventListener('fetch', event => {
if (event.request.url.includes("sw_messages.js")) {
var zib = "some data";
event.respondWith(new Response("window.msg=" + JSON.stringify(zib) + ";", {
headers: {
'Content-Type': 'application/javascript'
}
}));
}
return;
});
then, in the main html I just add:
<script src="sw_messages.js"></script>
as the page loads, global variable msg will contain (in this example) "some data".
How do I register a JWT token expired event with AngularFire?
Right now I have my Rails server sending a JWT to the AngularJS client, which is set to expire in 60 seconds. I'm connecting to Firebase on my client successfully. After 60 seconds, it expires and I get a "FIREBASE WARNING: auth() was canceled: Auth token is expired." message in my browser console.
It is not clear to me what to watch for this event. My goal would be to call my server for a new token when my current one expires.
client code snippet: here is me initializing a firebase obj and watching a url
var ref = new Firebase(URL);
// Create a callback which logs the current auth state
var authDataCallback = function (authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider);
} else {
console.log("User is logged out", authData);
}
}
// Register the callback to be fired every time auth state changes
ref.onAuth(authDataCallback);
var authHandler = function(error, authData) {
if(error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
var firebaseBal = $firebase(ref).$asObject();
// update balance when val changes
firebaseWatch = firebaseBal.$watch(function(newVal, oldVal) {
$scope.balance = firebaseBal.$value;
});
}
}
// Authenticate users with a custom Firebase token
ref.authWithCustomToken(Auth.firebaseToken(), authHandler);
server code snippet (I use firebase-token-generator to generate my JWT token):
generator = Firebase::FirebaseTokenGenerator.new(secret);
payload = { :uid => "123" }
# expired in a minute, for testing
expires = Time.now.to_i + (60*1)
options = {:expires => expires}
token = generator.create_token(payload, options)
// this is then sent to the client
Problem to be solved soon, see GitHub issue - https://github.com/firebase/angularfire/issues/514
This GitHub issue exists for the exact reason to help you accomplish
what you want to accomplish :) The short answer is that we don't
really support this that well right now.
For the time being, you will have to use $extendFactory() and put your
logic in the $$error()method. Here are the slim docs on that. The
$$error() method will fire once authentication fails due to token
expiry or security rules.
You should see improvements in this area in the coming weeks and this
is tagged for the 1.0.0 release.
var ref = new Firebase(URL);
var authHandler = function(error, authData) {
if(error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
// create a factory to pass into $firebase
var timeoutErrorFactory = $FirebaseObject.$extendFactory({
$$error: function(data) {
// TODO: not sure if super needs to be applied
// var err = $FirebaseObject.prototype.$$error.apply(this, arguments);
console.log('firebase timeout error cleanup', data.code);
// TODO: call server for a new token
}
});
// create an instance which uses the customized factory
firebaseBal = $firebase(ref, {objectFactory: timeoutErrorFactory}).$asObject();
// update balance when val changes
firebaseWatch = firebaseBal.$watch(function(newVal, oldVal) {
$scope.balance = firebaseBal.$value;
});
}
}
// Authenticate users with a custom Firebase token
ref.authWithCustomToken(Auth.firebaseToken(), authHandler);