How do I use express.js with truffle contracts? - javascript

I was following a tutorial on Youtube on making a decentralized voting app on truffle: https://youtu.be/3681ZYbDSSk.
The source code is as follows: https://github.com/dappuniversity/election/tree/2019_update
The system has a backend with lite-server, the same as the official pet-shop tutorial and a simple frontend where users vote for their candidates.
This is fine until I wanted to add a login and sign up feature into the system. After some research, I found out that lite-server is not able to send Mysql queries like express.js or php, so I tried to implement this feature in express.js.
I created a new dir, init truffle, npm install express, then put everything from the original project to the new one. I created index.js, which I run as the server
var express = require("express");
var app = express();
const path = require("path");
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, "public")));
app.use(express.static(path.join(__dirname, "build")));
// http://localhost:3000/
app.get("/", function (request, response) {
// Render login template
response.sendFile(path.join(__dirname + "/index.html"));
});
app.listen(3000);
I hope to include the compiled contracts using app.use(express.static(path.join(__dirname, "build")));, however somehow it can't recognize it, so I was forced to move the compiled contract into the public dir. Then I nodemon index.js again.
The system failed to render the candidates detail, so it's blank. This is how it should have look like.
In the app, the client-side app.js makes call to the web3.js api to get the details of candidates deployed on the blockchain. In my express app, it can only get the blockchain address but not the details. Here is the app.js (which can also be found on the github link above).
App = {
web3Provider: null,
contracts: {},
account: "0x0",
hasVoted: false,
init: function () {
return App.initWeb3();
},
initWeb3: function () {
// TODO: refactor conditional
if (typeof web3 !== "undefined") {
// If a web3 instance is already provided by Meta Mask.
App.web3Provider = web3.currentProvider;
web3 = new Web3(web3.currentProvider);
} else {
// Specify default instance if no web3 instance provided
App.web3Provider = new Web3.providers.HttpProvider(
"http://localhost:7545"
);
web3 = new Web3(App.web3Provider);
}
console.log("We are in initWeb3");
return App.initContract();
},
initContract: function () {
$.getJSON("Election.json", function (election) {
// Instantiate a new truffle contract from the artifact
App.contracts.Election = TruffleContract(election);
// Connect provider to interact with contract
App.contracts.Election.setProvider(App.web3Provider);
App.listenForEvents();
return App.render();
});
},
// Listen for events emitted from the contract
listenForEvents: function () {
App.contracts.Election.deployed().then(function (instance) {
// Restart Chrome if you are unable to receive this event
// This is a known issue with Metamask
// https://github.com/MetaMask/metamask-extension/issues/2393
instance
.votedEvent(
{},
{
fromBlock: 0,
toBlock: "latest",
}
)
.watch(function (error, event) {
console.log("event triggered", event);
// Reload when a new vote is recorded
App.updateCounts();
});
});
},
render: function () {
var electionInstance;
var loader = $("#loader");
var content = $("#content");
console.log("In render");
loader.show();
content.hide();
// Load account data
web3.eth.getCoinbase(function (err, account) {
if (err === null) {
App.account = account;
$("#accountAddress").html("Your Account: " + account);
}
});
// Load contract data
App.contracts.Election.deployed()
.then(function (instance) {
electionInstance = instance;
console.log(electionInstance.candidatesCount());
return electionInstance.candidatesCount();
})
.then(function (candidatesCount) {
var candidatesResults = $("#candidatesResults");
candidatesResults.empty();
var candidatesSelect = $("#candidatesSelect");
candidatesSelect.empty();
for (var i = 1; i <= candidatesCount; i++) {
electionInstance.candidates(i).then(function (candidate) {
var id = candidate[0];
var name = candidate[1];
var voteCount = candidate[2];
// Render candidate Result
var candidateTemplate =
"<tr><th>" +
id +
"</th><td>" +
name +
"</td><td id='vc_" +
id +
"'>" +
voteCount +
"</td></tr>";
candidatesResults.append(candidateTemplate);
// Render candidate ballot option
var candidateOption =
"<option value='" + id + "' >" + name + "</ option>";
candidatesSelect.append(candidateOption);
});
}
return electionInstance.voters(App.account);
})
.then(function (hasVoted) {
// Do not allow a user to vote
if (hasVoted) {
$("form").hide();
}
loader.hide();
content.show();
})
.catch(function (error) {
console.warn(error);
});
},
updateCounts: function () {
$("#content").hide();
$("#loader").show();
App.contracts.Election.deployed()
.then(function (instance) {
electionInstance = instance;
return electionInstance.candidatesCount();
})
.then(function (candidatesCount) {
for (var i = 1; i <= candidatesCount; i++) {
electionInstance.candidates(i).then(function (candidate) {
var id = candidate[0];
var voteCount = candidate[2];
var cell = document.getElementById("vc_" + id);
if (cell != null) {
cell.innerHTML = voteCount;
}
});
}
return electionInstance.voters(App.account);
})
.then(function (hasVoted) {
// Do not allow a user to vote
if (hasVoted) {
$("form").hide();
}
});
$("#content").show();
$("#loader").hide();
},
castVote: function () {
var candidateId = $("#candidatesSelect").val();
App.contracts.Election.deployed()
.then(function (instance) {
return instance.vote(candidateId, { from: App.account });
})
.then(function (result) {
// Wait for votes to update
$("#content").hide();
$("#loader").show();
})
.catch(function (err) {
console.error(err);
});
},
};
$(function () {
$(window).load(function () {
App.init();
console.log("Init success");
});
});
I noticed that web3.eth.getCoinbase works perfectly because the address is correctly shown, but somehow at the line App.contracts.Election.deployed(), it cannot get the details from the contract.
My question is, how do I fix my code so it can correctly show the details of the candidates? Because the sign up and login system can only work if the basic voting component is in place. Plus, how do I put it correctly so express.js can recognize my contracts in build/contracts?
Or are there other ways to make it work such as configuring lite-server to send mysql queries, or implementing this on xampp then use php to connect to mysql? Thank you very much.
P.S. I've tried to use express-box for this project. For whatever reasons, it cannot work. Whenever I tried to truffle compile, the vs code pops up and truffle did nothing, so I turned to creating a new express project instead.

After a series of painstaking trial-and-error experiments. I've finally found the solution. On the line " $.getJSON("Election.json", function (election)", it sends out a http get request to the server asking for "Election.json". This compiled contract resides in build/contracts, so the server has to serve this json if the voting system has to work, otherwise, it would turn up a blank section for candidates.
The solution is simply add one more line in index.js that imports the json. Then, add one more endpoint that just serves that json when requested. The final code would look like this
var express = require("express");
var app = express();
const path = require("path");
var json = require("./build/contracts/Election.json");
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, "public")));
// http://localhost:3000/
app.get("/", function (request, response) {
// Render login template
response.sendFile(path.join(__dirname + "/index.html"));
});
app.get("/Election.json", function (request, response) {
response.send(json);
});
app.listen(3000);
Now the system can find the json it needs to let user see the votes and vote as well.
If you have a better solution, please let me know!

Related

Stripe succesfully creating customer without card details

Complete web/Stripe newbie here. I built an iOS app, but the method of taking payments that I wanted to use isn't allowed on iOS, so I had to set up a website for it.
The website uses HTML/CSS, with a node backend hosted on Heroku. The website is a simple site that takes the name and card details of the user, but there's currently an issue with my implementation.
In app.get(), I create a customer and a setupIntent, and then this gets filled out when the user clicks a button on the site (just an event listener in js on the client-side).
My issue is, when I create a customer it creates an empty customer every time the page is loaded. If I remove this customer, there is no extra customer being added on load, and the correct customer is created, but there is no card attached to the customer's account!
I'm sure this is a basic error on my part, as I rushed through learning web dev in order to get the app to accept payments (we got an unexpected rejection from the App Review team, basically saying our app will never be acccepted as long as it takes card details on the app).
Thanks in advance for any/all help.
Cheers!
Josh
Server-side:
const express = require("express");
const bodyParser = require("body-parser");
const ejs = require("ejs");
require('dotenv').config()
const app = express();
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static("public"));
const Stripe = require('stripe');
const stripe = Stripe(process.env.SECRET_KEY);
app.get('/', async (req, res) => {
var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
const customer = await stripe.customers.create({
email: fullUrl.split('=')[1] //This gets the email sent in the URL from the app
});
const intent = await stripe.setupIntents.create({
customer: customer.id,
payment_method_types: ['card'],
});
console.log(fullUrl)
console.log(fullUrl.split('=')[1])
res.render('index', { client_secret: intent.client_secret });
})
app.listen(process.env.PORT || 3000);
Client-side:
var stripe = Stripe('livePublicKeyIsHere');
// const firebase = require("firebase");
// require("firebase/firestore");
var elements = stripe.elements();
var cardElement = elements.create('card');
cardElement.mount('#card-element');
var db = firebase.firestore();
var cardholderName = document.getElementById('cardholder-name');
var setupForm = document.getElementById('setup-form');
var clientSecret = setupForm.dataset.secret;
const queryString = window.location.search;
const email = queryString.split('=')[1];
setupForm.addEventListener('submit', function(ev) {
ev.preventDefault();
stripe.confirmCardSetup(
clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: cardholderName.value
},
},
}
).then(function(result) {
if (result.error) {
console.log("Error!!!" + result.error.message);
window.alert("There's an error: " + result.error.message);
} else {
console.log("Success!!!");
window.alert("Account created! Download and log into the app in order to continue.");
addUserToFirestore(email)
}
});
});
function addUserToFirestore(email) {
createUserOnFirestore(email);
db.collection("Users").doc(email).collection("Settings").doc("info").set({
cardDetailsAdded: true
})
.then(() => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
}
function createUserOnFirestore(email) {
db.collection("Users").doc(email).set({
exists: true
})
.then(() => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
}
The reason is because you're using get instead of post. When your user clicks the button, it should fire a POST request to your server to generate a SetupIntent object which you have already done. You also should store a relation mapping between your user and the Customer that is created, so you don't always create a new Customer when user adds a new card, instead you add the new card to existing Customer object.
While using a customer is recommended, ultimately it is optional to provide a customer (API ref). You can also attach a payment method to a customer separately, as long as you do so before using it for a payment.
Note that unless attached to a customer, a payment method is one-time-use only.

Merge Two codes

I have 2 files in Node js .I want to merge these 2, but I am facing problem..
This file calls function from python file
const app = express()
let runPy = new Promise(function(success, nosuccess) {
const { spawn } = require('child_process');
const pyprog = spawn('python', ['./ml.py']);
pyprog.stdout.on('data', function(data) {
success(data);
});
pyprog.stderr.on('data', (data) => {
nosuccess(data);
});
});
app.get('/', (req, res) => {
res.write('welcome\n');
runPy.then(function(testMLFunction) {
console.log(testMLFunction.toString());
res.end(testMLFunction);
});
})
app.listen(4000, () => console.log('Application listening on port 4000!'))
python file ml.py
def testMLFunction():
return "hello from Python"
print(testMLFunction())
Below file works on button click with post method
var fs = require('fs');
var server = http.createServer(function (req, res) {
if (req.method === "GET") {
res.writeHead(200, { "Content-Type": "text/html" });
fs.createReadStream("./form.html", "UTF-8").pipe(res);
} else if (req.method === "POST") {
var result = "";
req.on("data", function (chunk) {
console.log(chunk.toString());
result = chunk;
//body=body.toUpperCase;
});
req.on("end", function(){
res.writeHead(200, { "Content-Type": "text/html" });
res.end(result);
});
}
}).listen(3000);
how can I do that..
There are several things wrong here. I will explain as plain as possible.
You forgot to add in your code var express = require('express')
The promise you made, runPy, must be wrapped in a function, whereas your approach will instantly start the promise upon loading the script itself.
You are resolving/rejecting on first incoming output, you shouldn't do that because you won't be able to know what really happened in the shell. You need to store those output lines, this is the only way of you knowing what the script tells you.
In runPy you must resolve/reject upon pyprogr close event.
You cannot access directly the method of another script, no matter what that kind of file that is a py, sh, bat, js. However, you can access internal functions of it by passing arguments to the shell, and of course, that script must have the logic required to deal with those arguments.
When using spawn/exec you must keep in mind that YOU ARE NOT the user executing the script, the node user is, so different outcomes may occur.
Most importantly, your targeted script must PRINT/ECHO to shell, no returns! The best approach would be to print some json string, and parse it in javascript after the shell is closed, so you can have access to an object instead of a string.
Below you will find a demo for your use case, i changed the python file so it can print something.
ml.py
print('I\'m the output from ml.py')
index.js
const express = require('express');
const app = express()
let runPy = function () { // the promise is now wrapped in a function so it won't trigger on script load
return new Promise(function (success, nosuccess) {
const {spawn} = require('child_process');
const pyprog = spawn('python', ['./ml.py'], {shell: true}); // add shell:true so node will spawn it with your system shell.
let storeLines = []; // store the printed rows from the script
let storeErrors = []; // store errors occurred
pyprog.stdout.on('data', function (data) {
storeLines.push(data);
});
pyprog.stderr.on('data', (data) => {
storeErrors.push(data);
});
pyprog.on('close', () => {
// if we have errors will reject the promise and we'll catch it later
if (storeErrors.length) {
nosuccess(new Error(Buffer.concat(storeErrors).toString()));
} else {
success(storeLines);
}
})
})
};
let path = require('path');
app.use(express.json());
app.use(express.urlencoded({ extended: true })); // you need to set this so you can catch POST requests
app.all('/', (req, res) => { // i've change this from .get to .all so you can catch both get and post requests here
console.log('post params', req.body);
if(req.body.hasOwnProperty('btn-send')){
runPy()
.then(function (pyOutputBuffer) {
let message = 'You sent this params:\n' +JSON.stringify(req.body, null,2) + '\n';
message += Buffer.concat(pyOutputBuffer).toString();
res.end(message);
})
.catch(console.log)
}else{
res.sendFile(path.join(__dirname,'form.html')); // you need an absolute path to 'file.html'
}
});
app.listen(4000, () => console.log('Application listening on port 4000!'));
form.html
<div>hello there</div>
<form action="/" method="post">
<input type="text" value="" name="some-text"/>
<button type="submit" value="1" name="btn-send" >Press me!</button>
</form>

xterm.js reconnect with same PWD

I am using xterm.js in my web project to have a terminal on the web page. Every time I refresh my page or reconnect socket when a socket connection is broken due to internet fluctuation from the client. The current PWD directory is lost and it falls to specified CWD directory which is user home in my case. So again I have to do cd where I was working.
How can I connect and remain at same PWD where I was last time before page refreshing or socket disconnect?
One of the things I tried is to store term object and connect through the same object when reconnecting if it is already present. Not deleting process and object in on WebSocket disconnect.
var http = require('http');
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var pty = require('node-pty');
var cors = require('cors');
app.use(cors());
app.options('*', cors());
var terminals = {}; //global terminals
function getUser(token) {
return new Promise((resolve, reject) => {
try {
return http.get({
host: '',
path: '',
headers: {'token': token}
}, function(response) {
// Continuously update stream with data
var body = '';
response.on('data', function(d) {
body += d;
});
response.on('end', function() {
return resolve(JSON.parse(body));
});
});
} catch (err) {
console.log('Api failed');
console.log(err);
reject;
}
})
}
app.ws('/terminals/:user_id', function (ws, req) {
try {
getUser(req.params.user_id) /* cheking with api if user exist in my database*/
.then(user_info => {
if(terminals[parseInt(req.params.user_id)]){
var term = terminals[parseInt(req.params.user_id)];
}else {
var term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
name: 'xterm-color',
cwd: cwd,
env: process.env
});
terminals[parseInt(req.params.user_id)] = term;
}
term.on('data', function(data) {
ws.send(data);
});
ws.on('message', function(msg) {
term.write(msg);
});
ws.on('close', function () {
// process.kill(term.pid);
// delete terminals[parseInt(req.params.pid)];
// delete logs[req.params.pid];
});
})
.catch(err => {
})
} catch (err) {
console.log('Terminal webSocket failed');
console.log(err);
}
});
app.listen(3000);
This is not working for me. This gets me connect only first time but when I refresh my page terminal does not connect with existing store object.
Also, this has a problem if the spawned process is killed by the system but it still remains in javascript object and script try to reconnect with same term object it will fail.
Any guidelines how to achieve reconnect with same PWD.
Details
OS version: Mac OS ,
xterm.js version: 2.2.3
This can be solved very easily by just updating the ~/.bashrc on server
Putting below two line in ~/.bashrc file worked for me
PROMPT_COMMAND+='printf %s "$PWD" > ~/.storepwd'
[ -s ~/.lastdirectory ] && cd `cat ~/.lastdirectory`
Ref Save last working directory on Bash logout

Hapi.js - adding mechanism to check every route

I am trying to implement a mechanism that will be run before any route is hit. In that mechanism I want to take a value from the header and check for authentication.
I have come up with this:
server.js:
// Create a server with a host and port
'use strict';
var Hapi = require('hapi');
var mongojs = require('mongojs');
var plugins = [
require('./routes/entities')
];
var server = new Hapi.Server();
server.connection({
port: 3000
});
//Connect to db
server.app.db = mongojs('hapi-rest-mongo', ['entities']);
server.app.checkHeader = function (request) {
var header = request.headers['x-authorization'];
if(header === "letmein"){
return true
}
return false
};
//Load plugins and start server
server.register(plugins, function (err) {
if (err) {
throw err;
}
// Start the server
server.start(function (err) {
console.log('Server running at:', server.info.uri);
});
});
and in routes.entities:
'use strict';
var Boom = require('boom');
var uuid = require('node-uuid');
var Joi = require('joi');
exports.register = function (server, options, next) {
var db = server.app.db;
server.route({
method: 'GET',
path: '/entities',
handler: function handler(request, reply) {
if(!server.app.checkHeader(request))
{
return reply(Boom.unauthorized());
};
//request.server.myFunc();
db.entities.find(function (err, docs) {
if (err) {
return reply(Boom.wrap(err, 'Internal MongoDB error'));
}
reply(docs);
});
}
});
So in short while starting the server I have registered my function server.app.checkHeader
And in the routes I am calling it and sending a request object to it. Request object contains information about the headers.
While this works, I am having a feeling I am not following the best practices with the Hapi.
How could I do it more elegantly?
There are a few options.
You can, of course, tap into the request lifecycle - note the events that occur in the pipeline prior to the route handler.
Although, I'd urge you to consider implementing an auth strategy that can be set as the default for all routes or selectively on appropriate routes.
The best way to require authentication for all or selected route is to use hapi’s integrated functionality.
You should set a default authentication strategy that is applied to each route handler. The sample below uses basic auth. You’d want to create a custom authentication strategy for hapi to check your x-authentication header.
const Hapi = require('hapi')
const BasicAuth = require('hapi-auth-basic')
const server = new Hapi.Server()
server.register(BasicAuth, function (err) {
if (err) {
console.log('error', 'failed to install plugins')
throw err
}
// TODO: add authentication strategy & set as default
server.auth.strategy('simple', 'basic', true, { validateFunc: basicValidationFn })
// or set strategy separately as default auth strategy
server.auth.strategy('simple', 'basic', { validateFunc: basicValidationFn })
server.auth.default('simple')
// TODO: add routes
server.start(function (err) {
})
})
You can also inject hapi’s request lifecycle and extend it at given points. Extending the request lifecycle should be done by using plugins:
register: function (server, options, next) {
// do some processing before 'onPreAuth'
// or pick another extension point
server.ext('onPreAuth', (request, reply) => {
// your functionality
})
}
Hope that helps!

Securing Loopback with third party OpenID Connect

I'm trying to secure my loopback service with my third party OpenID Connect service (Keycloak) but it doesn't seem to be validating requests have accesstokens at all.
My server.js:
var loopback = require('loopback');
var boot = require('loopback-boot');
var app = module.exports = loopback();
// Passport configurators..
var loopbackPassport = require('loopback-component-passport');
var PassportConfigurator = loopbackPassport.PassportConfigurator;
var passportConfigurator = new PassportConfigurator(app);
var cont = function(req, res){
next();
};
/**
* Flash messages for passport
*
* Setting the failureFlash option to true instructs Passport to flash an
* error message using the message given by the strategy's verify callback,
* if any. This is often the best approach, because the verify callback
* can make the most accurate determination of why authentication failed.
*/
var flash = require('express-flash');
// attempt to build the providers/passport config
var config = {};
try {
config = require('../providers.json');
} catch (err) {
console.trace(err);
process.exit(1); // fatal
}
// -- Add your pre-processing middleware here --
// boot scripts mount components like REST API
boot(app, __dirname);
// The access token is only available after boot
app.middleware('auth', loopback.token({
model: app.models.accessToken
}));
app.middleware('session:before', loopback.cookieParser(app.get('cookieSecret')));
app.middleware('session', loopback.session({
secret: 'kitty',
saveUninitialized: true,
resave: true
}));
passportConfigurator.init();
// We need flash messages to see passport errors
app.use(flash());
passportConfigurator.setupModels({
userModel: app.models.user,
userIdentityModel: app.models.userIdentity,
userCredentialModel: app.models.userCredential
});
for (var s in config) {
var c = config[s];
c.session = c.session !== false;
passportConfigurator.configureProvider(s, c);
}
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn;
app.start = function () {
// start the web server
return app.listen(function () {
app.emit('started');
var baseUrl = app.get('url').replace(/\/$/, '');
console.log('Web server listening at: %s', baseUrl);
if (app.get('loopback-component-explorer')) {
var explorerPath = app.get('loopback-component-explorer').mountPath;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
}
});
};
// Bootstrap the application, configure models, datasources and middleware.
// Sub-apps like REST API are mounted via boot scripts.
boot(app, __dirname, function (err) {
if (err) throw err;
// start the server if `$ node server.js`
if (require.main === module)
app.start();
});
provider.json
{
"oAuth2": {
"provider": "keycloak",
"module": "passport-openidconnect",
"authorizationURL": "https://xxx",
"tokenURL": "https://xxxx",
"clientID": "xxx",
"clientSecret": "-",
"failureFlash": true
}
}
I've been trying to follow this example:
https://github.com/strongloop/loopback-example-passport
But that doesn't explain how to connect to an OpenID Connect service and secure my APIs.
I've also tried this for specific APIs:
app.get('/api/Clients', ensureLoggedIn('/login'), cont);
I want to really lock down all APIs and check if a valid token is presented in the query which should be validated by my third party authentication service.
Thanks in advance!

Categories