Is it OK to pass both token and client_id to the client when Channel API is used? - python

I need to create an application, where GAE server will always talk with just one client (i.e. one message should be always sent just to one client).
I do the following -
Python:
def get(self):
# generate token, when page is loaded
client_id = uuid.uuid4().hex
token = channel.create_channel(client_id)
template_values = {'token': token,
'client_id': client_id
}
self.response.out.write(template.render('page.html', template_values))
def post(self):
# reply to the client
...
client_id = self.request.get('id')
channel.send_message(client_id, message)
Javascript:
sendMessage = function(field) {
$.ajax({
type: "POST",
url: "/",
data: "f=" + field + "&id=" + "{{ client_id }}", // WARNING!
contentType: "application/x-www-form-urlencoded",
success: function(data) {
}
});
};
onOpened = function() {
connected = true;
sendMessage('opened');
};
onMessage = function(msg) {
alert(msg.data);
};
onError = function(err) {
alert(err);
};
onClose = function() {
alert("close");
};
// open new session
channel = new goog.appengine.Channel('{{ token }}'); // WARNING!
socket = channel.open();
socket.onopen = onOpened;
socket.onmessage = onMessage;
socket.onerror = onError;
socket.onclose = onClose;
It works well, but with such scenario both token and client_id are passed to the client. Is it OK?

There's no technical reason not to do this. If you're worried about security, the token is far more valuable: an attacker who could listen to your traffic could take the token and listen to channel messages in a different context. The clientid wouldn't let them do that.
But I do have a question: why not return the message in the POST response, rather than sending a message over the channel? Or is the sample code just simplified for the example?

Related

400 error when posting PushSubscription object to server

I am setting up push notifications using service workers + websockets using Django Channels and Django WebPush to receive the subscription and send a message back to the service worker.
While I am able to register a service worker and receive the PushSubscription object containing details of a push subscription. I then try to send this subscription to an end point in webpush 'save_information' that should have the subscription. However, when I attempt to do this I get the error:
Response {type: 'cors', url: 'http://127.0.0.1:8000/webpush/save_information', redirected: false, status: 400, ok: false, …}body: (...)bodyUsed: falseheaders: Headers {}ok: falseredirected: falsestatus: 400statusText: "Bad Request"type: "cors"url: "http://127.0.0.1:8000/webpush/save_information"[[Prototype]]: Response
This is the code I have in my service worker/ the code related to getting a subscription and sending through the subscription:
const saveSubscription = async (subscription) => {
const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
const data = {
status_type: 'subscribe',
subscription: subscription.toJSON(),
browser: browser,
};
const res = await fetch('http://127.0.0.1:8000/webpush/save_information', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'content-type': 'application/json'
},
credentials: 'include'
});
handleResponse(res);
}
const handleResponse = (res) => {
console.log(res);
}
...
self.addEventListener('activate', async () => {
try {
const applicationServerKey = urlB64ToUint8Array('***')
const options = { applicationServerKey,
userVisibleOnly: true }
const subscription = await self.registration.pushManager.subscribe(options)
saveSubscription(subscription)
} catch (err) {
console.log('Error', err)
}
})
and in my server:
path('webpush/', include('webpush.urls')),
Would appreciate any help

FCM onMessage not trigerring in React but app server sends multicast_messages properly

I am sending the message from a Python script as follows.
import firebase_admin
from firebase_admin import credentials, messaging
# initializations
cred = credentials.Certificate('Full path to firebase-admin-sdk.json')
print('Connecting...')
firebase_admin.initialize_app(cred)
registration_tokens = [
'valid_registration_token_from_client',
]
message = messaging.MulticastMessage(
data={'score': '850', 'time': '2:45'},
tokens = registration_tokens
)
response = messaging.send_multicast(message)
print('{0} messages were sent successfully.'.format(response.success_count))
After executing the above code, it prints 1 message sent successfully. I looked at my Firebase console and found that the number of sent notifications increased by 1. However, my React Js client does not seem to receive the message.
In react app,
Root public folder has firebase-messaging-sw.js file,
import { initializeApp } from "firebase/app";
import { getMessaging } from "firebase/messaging/sw";
import { onBackgroundMessage } from "firebase/messaging/sw";
const firebaseConfig = {
apiKey: "MY_API_KEY",
authDomain: "FIREBASE_APP_DOMAIN",
databaseURL: "DB_URL",
projectId: "PROJECT_ID",
storageBucket: "STORAGE_BUCKET",
messagingSenderId: "SENDER_ID",
appId: "APP_ID",
measurementId: "MEASUREMENT_ID"
};
const firebaseApp = initializeApp(firebaseConfig);
const messaging = getMessaging(firebaseApp);
onBackgroundMessage(messaging, (payload) => {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
const notificationTitle = 'Background Message Title';
const notificationOptions = {
body: 'Background Message body.',
icon: '/firebase-logo.png'
};
self.registration.showNotification(notificationTitle,
notificationOptions);
});
In App.js,
import React, {Component} from "react";
import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { initializeApp } from "firebase/app";
export default class App extends Component {
constructor(props) {
super(props);
this.connectButtonPressed = this.connectButtonPressed.bind(this);
}
render() {
return (
<div><button onClick={this.connectButtonPressed}>Click</button></div>
)
}
connectButtonPressed(e) {
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
console.log('Notification permission granted.');
// TODO(developer): Retrieve a registration token for use with FCM.
// Get registration token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
const firebaseConfig = {
apiKey: "API_KEY",
authDomain: "AUTH_DOMAIN",
databaseURL: "DB_URL",
projectId: "PROJECT_ID",
storageBucket: "STORAGE_BUCKET",
messagingSenderId: "SENDER_ID",
appId: "APP_ID",
measurementId: "MEASUREMENT_ID"
};
const firebaseApp = initializeApp(firebaseConfig);
const messaging = getMessaging(firebaseApp);
onMessage(messaging, (payload) => {
console.log('Message received. ', payload);
});
getToken(messaging, { vapidKey: 'VAPID_KEY_FROM_CONSOLE' }).then((currentToken) => {
if (currentToken) {
// Send the token to your server and update the UI if necessary
console.log('currentToken: ', currentToken);
} else {
// Show permission request UI
console.log('No registration token available. Request permission to generate one.');
// ...
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
// ...
});
} else {
console.log('Unable to get permission to notify.');
}
});
}
}
The client can successfully request for and receive the registration token to which I send the message from the Python script. However, the onMessage event is not being triggered which makes me think that the client might not be receiving the message even though I sent the message to the token associated with this client.
What might be the issue here? What am I missing?

http post a base64 json request between Node.js and Python Flask server but Node received an incomplete json

I try to send a base64 json form node.js server to python flask server and than return a same base64 code back to node.js server. Flask can successfully receive my json but when it response to node and node try to print out the response. I got a error message say: "Unexpected end of JSON input". I found the reason is node server can not receive the base64 completely. It just only receive a small portion. What is the problem? Is post request has a string limit?
I tested when I change the base64 code to a short string. Node server can receive response normally.
Anyone can help me? Thank you.
This is my code:
<<< Node.js Server >>>
var express = require('express');
var http = require('http');
var app = express();
app.get('/', (req, res) => res.send('Hello World!'));
app.listen(10000, () => console.log('Running on http://localhost:10000'));
postData = JSON.stringify({
'code': <base64 code or short string here>
});
var options = {
hostname: 'localhost',
port: 10001,
path: '/test',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
var req = http.request(options, (res) => {
res.on('data', (chunk) => {
var data = JSON.parse(chunk);
console.log(data.message);
});
res.on('end', () => {
console.log('No more data in response.');
});
});
req.on('error', (e) => {
console.error(`problem with request: ${e.message}`);
});
req.write(postData);
req.end();
<<< Python Flask Server >>>
from flask import Flask
from flask import request
from flask import jsonify
app = Flask(__name__)
#app.route('/test', methods=['POST'])
def test():
request_data = request.get_json()
print(request_data['code'])
return jsonify(
message = request_data['code']
)
app.run(host='localhost', port=10001)
In NodeJS code, in data event, you will get chunk data(partial data), you need to wait until end event and then parse, following example may help you
var req = http.request(options, (res) => {
var data = '';
res.on('data', (chunk) => {
data += chunk.toString(); // buffer to string
});
res.on('end', () => {
data = JSON.parse(data);
console.log(data.message);
console.log('No more data in response.');
});
});

WebSocket: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received

I'm trying to create a WS connection with my tornado server. The server code is simple:
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
print("WebSocket opened")
def on_message(self, message):
self.write_message(u"You said: " + message)
def on_close(self):
print("WebSocket closed")
def main():
settings = {
"static_path": os.path.join(os.path.dirname(__file__), "static")
}
app = tornado.web.Application([
(r'/ws', WebSocketHandler),
(r"/()$", tornado.web.StaticFileHandler, {'path':'static/index.html'}),
], **settings)
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
I copy pasted the client code from here:
$(document).ready(function () {
if ("WebSocket" in window) {
console.log('WebSocket is supported by your browser.');
var serviceUrl = 'ws://localhost:8888/ws';
var protocol = 'Chat-1.0';
var socket = new WebSocket(serviceUrl, protocol);
socket.onopen = function () {
console.log('Connection Established!');
};
socket.onclose = function () {
console.log('Connection Closed!');
};
socket.onerror = function (error) {
console.log('Error Occured: ' + error);
};
socket.onmessage = function (e) {
if (typeof e.data === "string") {
console.log('String message received: ' + e.data);
}
else if (e.data instanceof ArrayBuffer) {
console.log('ArrayBuffer received: ' + e.data);
}
else if (e.data instanceof Blob) {
console.log('Blob received: ' + e.data);
}
};
socket.send("Hello WebSocket!");
socket.close();
}
});
When it tries to connect i get the following output on the browser's console:
WebSocket connection to 'ws://localhost:8888/ws' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
Why is that?
As pointed out in whatwg.org's Websocket documentation (it's a copy from the standard's draft):
The WebSocket(url, protocols) constructor takes one or two arguments. The first argument, url, specifies the URL to which to connect. The second, protocols, if present, is either a string or an array of strings. If it is a string, it is equivalent to an array consisting of just that string; if it is omitted, it is equivalent to the empty array. Each string in the array is a subprotocol name. The connection will only be established if the server reports that it has selected one of these subprotocols. The subprotocol names must all be strings that match the requirements for elements that comprise the value of Sec-WebSocket-Protocol fields as defined by the WebSocket protocol specification.
Your server answers the websocket connection request with an empty Sec-WebSocket-Protocol header, since it doesn't support the Chat-1 subprotocol.
Since you're writing both the server side and the client side (and unless your writing an API you intend to share), it shouldn't be super important to set a specific subprotocol name.
You can fix this by either removing the subprotocol name from the javascript connection:
var socket = new WebSocket(serviceUrl);
Or by modifying your server to support the protocol requested.
I could give a Ruby example, but I can't give a Python example since I don't have enough information.
EDIT (Ruby example)
Since I was asked in the comments, here's a Ruby example.
This example requires the iodine HTTP/WebSockets server, since it supports the rack.upgrade specification draft (concept detailed here) and adds a pub/sub API.
The server code can be either executed through the terminal or as a Rack application in a config.ru file (run iodine from the command line to start the server):
# frozen_string_literal: true
class ChatClient
def on_open client
#nickname = client.env['PATH_INFO'].to_s.split('/')[1] || "Guest"
client.subscribe :chat
client.publish :chat , "#{#nickname} joined the chat."
if client.env['my_websocket.protocol']
client.write "You're using the #{client.env['my_websocket.protocol']} protocol"
else
client.write "You're not using a protocol, but we let it slide"
end
end
def on_close client
client.publish :chat , "#{#nickname} left the chat."
end
def on_message client, message
client.publish :chat , "#{#nickname}: #{message}"
end
end
module APP
# the Rack application
def self.call env
return [200, {}, ["Hello World"]] unless env["rack.upgrade?"]
env["rack.upgrade"] = ChatClient.new
protocol = select_protocol(env)
if protocol
# we will use the same client for all protocols, because it's a toy example
env['my_websocket.protocol'] = protocol # <= used by the client
[101, { "Sec-Websocket-Protocol" => protocol }, []]
else
# we can either refuse the connection, or allow it without a match
# here, it is allowed
[101, {}, []]
end
end
# the allowed protocols
PROTOCOLS = %w{ chat-1.0 soap raw }
def select_protocol(env)
request_protocols = env["HTTP_SEC_WEBSOCKET_PROTOCOL"]
unless request_protocols.nil?
request_protocols = request_protocols.split(/,\s?/) if request_protocols.is_a?(String)
request_protocols.detect { |request_protocol| PROTOCOLS.include? request_protocol }
end # either `nil` or the result of `request_protocols.detect` are returned
end
# make functions available as a singleton module
extend self
end
# config.ru
if __FILE__.end_with? ".ru"
run APP
else
# terminal?
require 'iodine'
Iodine.threads = 1
Iodine.listen2http app: APP, log: true
Iodine.start
end
To test the code, the following JavaScript should work:
ws = new WebSocket("ws://localhost:3000/Mitchel", "chat-1.0");
ws.onmessage = function(e) { console.log(e.data); };
ws.onclose = function(e) { console.log("Closed"); };
ws.onopen = function(e) { e.target.send("Yo!"); };
For those who use cloudformation templates, AWS has a nice example here.
UPDATE
The key thing is the response in the connection function. On the abovementioned AWS shows how this can be done:
exports.handler = async (event) => {
if (event.headers != undefined) {
const headers = toLowerCaseProperties(event.headers);
if (headers['sec-websocket-protocol'] != undefined) {
const subprotocolHeader = headers['sec-websocket-protocol'];
const subprotocols = subprotocolHeader.split(',');
if (subprotocols.indexOf('myprotocol') >= 0) {
const response = {
statusCode: 200,
headers: {
"Sec-WebSocket-Protocol" : "myprotocol"
}
};
return response;
}
}
}
const response = {
statusCode: 400
};
return response;
};
function toLowerCaseProperties(obj) {
var wrapper = {};
for (var key in obj) {
wrapper[key.toLowerCase()] = obj[key];
}
return wrapper;
}
Please note the header settings in the response. Also this response must be delivered to the requester, for this response integration must be configured.
In the AWS example consider the code:
MyIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref MyAPI
IntegrationType: AWS_PROXY
IntegrationUri: !GetAtt MyLambdaFunction.Arn
IntegrationMethod: POST
ConnectionType: INTERNET
The most important are the last two lines.

django saving messages to database via node server and socket.io

I have node server:
var http = require('http');
var server = http.createServer().listen(4000);
var io = require('socket.io').listen(server);
var cookie_reader = require('cookie');
var querystring = require('querystring');
var redis = require('redis');
var sub = redis.createClient();
//Subscribe to the Redis chat channel
sub.subscribe('chat');
//Configure socket.io to store cookie set by Django
io.use(function(){
io.set('authorization', function(data, accept){
if(data.headers.cookie){
data.cookie = cookie_reader.parse(data.headers.cookie);
return accept(null, true);
}
return accept('error', false);
});
io.set('log level', 1);
});
io.sockets.on('connection', function (socket) {
//Grab message from Redis and send to client
sub.on('message', function(channel, message){
socket.send(message);
});
//Client is sending message through socket.io
socket.on('send_message', function (message) {
values = querystring.stringify({
comment: message,
sessionid: socket.handshake.cookie['sessionid'],
});
var options = {
host: 'localhost',
port: 8000,
path: '/node_api',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': values.length
}
};
//Send message to Django server
var req = http.request(options, function(res){
res.setEncoding('utf8');
//Print out error message
res.on('data', function(message){
if(message != 'Everything worked :)'){
console.log('Message: ' + message);
}
});
});
req.write(values);
req.end();
});
});
When I send message it does not save in the database..
Here is my view for django:
#csrf_exempt
def node_api(request):
print "inside node"
try:
print "inside try"
session = Session.objects.get(session_key=request.POST.get("sessionid"))
print "first"
user_id = session.get_decode().get("_auth_user_id")
print user_id
user = User.objects.get(id=user_id)
Comments.objects.create(user=user, text=request.POST.get("comment"))
r = redis.StrictRedis(host="localhost", port=6379, db=0)
r.publish("chat", user.username + ": " + request.POST.get("comment"))
return HttpResponse("Everything worked :")
except Exception, e:
return HttpResponseServerError(str(e))
Whats wrong in here??
Can anyone guide me to right direction?
When I go to '/node_api/' url it says Session matching query doesnot exist..
My view for displaying message:
#login_required
def home(request):
print "entered"
comments = Comments.objects.select_related().all()[0:100]
return render(request, "index.html", locals())
When I submit the message its not saving to database neither displaying...
Thanx in advance??
If you are using python then why don't you try python way of doing realtime.
Try tornado with django. It supports socket.io..
You can code in python with no difficulty.
It doesn't mean javascript is not good but try do in python and have a look at tornado, redis, django. YOu will find your solution.

Categories