I am an "advanced beginner" with javascript API's so my needs maybe sophomoric, but after pounding my head against the wall all night appreciate any basic guidance you can provide. I am trying to authenticate an app using Oauth2 to hit my BaseCamp site.
I am using Grant Express and have registered my app successfully such that I received client_ID, client_secret and redirect uri. For the redirect uri I added a folder called "auth" but the only thing in it is an index.html file that is blank. So the redirect url is http://example.com/auth.
On my local machine I have created a directory called oauth and within it ran:
npm install express
npm install grant-express
I created a file app.js that looks like this:
var express = require('express')
, session = require('express-session')
var Grant = require('grant-express')
var config = {
server: {
protocol: "http",
host: "127.0.0.1:3000"
},
basecamp: {
key: "key_from_basecamp",
secret: "secret_from_basecamp",
callback: "/basecamp/callback"
}
}
var app = express()
app.use(session({secret:'grant',
resave: true,
saveUninitialized: true}))
app.use(new Grant(config))
app.get("/basecamp/callback", function (req, res) {
console.log(req.query)
res.end(JSON.stringify(req.query, null, 2))
})
app.listen(3000, function () {
console.log('Express server listening on port ' + 3000)
})
The package.json file looks like this:
{
"name": "auth",
"version": "1.0.0",
"description": "",
"main": "app.js",
"dependencies": {
"express": "^4.13.4",
"grant-express": "^3.6.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
I go to the terminal and enter node app.js and get the response: Express server listening on port 3000. All good right?
When I go to my browser and type in http://localhost:3000/connect/basecamp the url does redirect to: https://launchpad.37signals.com/authorization/new?client_id=client_id_from_basecamp&response_type=code&redirect_uri=http%3A%2F%2F127.0.0.1%3A3000%2Fconnect%2Fbasecamp%2Fcallback&type=web_server
but the page contains this error: :error: Provided redirect_uri is not approved
If I got to: http://localhost:3000/connect/basecamp/callback
I see this error (also in the console of the server) { error: { error: 'Grant: OAuth2 missing code parameter' } }
In the Basecamp API documentation it says:
Configure your OAuth 2 library with your client_id, client_secret, and redirect_uri. Tell it to use https://launchpad.37signals.com/authorization/new to request authorization and https://launchpad.37signals.com/authorization/token to get access tokens. What is "it" and how, exactly would I tell it to use these urls? Do I add these url's into my app.js file as objects? Or do I go into another file? Do I need to add something into http://example.com/auth?
Not sure where to go from here.... Many thanks for any assistance.
The problem was my redirect url. It should be
http://localhost:3000/connect/basecamp/callback On to new errors!
Related
I want to test an app that only has Google Oauth Login via AWS Cognito. Lots of guides on how to use cypress to programatically login to Cognito using AWS Amplify with a username and password, but cannot find anything on how to do it with Google Oauth.
Im trying to use cypress to click the buttons to authenticate but I think there is a click forgery protection on Google.
I have also been able to use this cypress documentation to login to Google directly and get a jwt into session storage but not sure if there is a way to pass this to Cognito.
If you're doing end to end testing, then the simplest way would be to have another non prod staging environment without the Google Oauth login in Cognito, and instead the username password login you mentioned that has working examples.
This is also a good idea, as you shouldn't be using your production user system in testing anyway.
I tried many approaches to this including getting oauth token from Google and trying to exchange that manually with Cognito for the token I needed for AWS API Gateway. I also tried cypress-social-logins without success (because of this bug)
Finally I just wrote my own cypress steps to log me into my app with a valid session. NOTE THIS WILL NOT WORK IN A CI/CD because it will probably trigger Google validations and so it is a semi-automated solution.
If in doubt I recommend Tom Roman's answer below about creating a pre-prod environment in cognito that allows username/password login instead of messing about with google login.
// /support/login.js
Cypress.Commands.add('loginByGoogle', () => {
cy.visit('http://localhost:3030')
cy.origin('https://somecognitouserpool.auth.eu-west-1.amazoncognito.com', () => {
cy.contains('button', 'Continue with Google')
.click({force: true})
})
cy.origin('https://accounts.google.com', () => {
const resizeObserverLoopError = /^[^(ResizeObserver loop limit exceeded)]/;
Cypress.on('uncaught:exception', (err) => {
/* returning false here prevents Cypress from failing the test */
if (resizeObserverLoopError.test(err.message)) {
return false;
}
});
cy.get('input#identifierId[type="email"]')
.type(Cypress.env('googleSocialLoginUsername'))
.get('button[type="button"]').contains('Next')
.click()
.get('div#password input[type="password"]')
.type(Cypress.env('googleSocialLoginPassword'))
.get('button[type="button"]').contains('Next')
.click();
});
});
// /e2e/sometest.cy.js
before(() => {
cy.loginByGoogle();
});
describe('E2E testing', () => {
it('should now have a session', () => {
})
});
You also need a .env file (because you don't want to be saving your google credentials into github)
GOOGLE_USERNAME = ''
GOOGLE_PASSWORD = ''
You also need two experimental flags (as of 14th Nov 2022)
// cypress.config.js
const { defineConfig } = require('cypress');
require('dotenv').config()
module.exports = defineConfig({
env: {
googleSocialLoginUsername: process.env.GOOGLE_USERNAME,
googleSocialLoginPassword: process.env.GOOGLE_PASSWORD
},
e2e: {
experimentalSessionAndOrigin: true,
experimentalModifyObstructiveThirdPartyCode: true
}
})
Here is my package.json so that you can see the exact packages I am using.
In particular I added the flags --headed --no-exit in order to complete 2 factor authentication manually as necessary. I have not yet figured out how to stop Google asking for this every time.
{
"name": "docs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "http-server . -p 3030",
"cy:run": "cypress run --headed --no-exit",
"test": "start-server-and-test start http://localhost:3030 cy:run"
},
"author": "",
"license": "ISC",
"devDependencies": {
"cypress": "^11.0.1",
"start-server-and-test": "^1.14.0"
},
"dependencies": {
"dot-env": "^0.0.1",
"dotenv": "^16.0.3"
}
}
I have decided to use firebase as a backend for authentication and basic form information with firestore.
In the past I've used this express api in cloud functions to do this, and am basing this new setup off of that. But I'm looking to just use it on a Vultr Centos server instead, to put with the rest of my api, and just make everything easier for now as i don't want to overcomplicate things (until later :P).
Now - I've just cloned this repo onto my server, and I want to test it with postman, and i'm having trouble just accessing it.
I'm not sure how to solve the issue and if anyone could point me in the right direction that would make my life so much easier!
here is the index file and the package json file currently. I've created the server.listen to try and make it work at the moment.
const functions = require("firebase-functions");
const app = require("express")();
const FBAuth = require("./util/fbAuth");
const server = require('http').createServer(app);
const cors = require("cors");
//This was recently added to try and make it all work easier!
server.listen(port, ipaddress, () => {
});
app.use(cors());
const { db } = require("./util/admin");
const {
getAllWorkflows,
...
} = require("./handlers/workflow");
const {
signup,
login,
uploadImage,
addUserDetails,
getAuthenticatedUser,
getUserDetails
} = require("./handlers/users");
// Workflow Routes
app.get("/Workflows", getAllWorkflows);
...
// user route
app.post("/user", FBAuth, addUserDetails);
app.post("/user/image", FBAuth, uploadImage);
...
// cloud functions are better than firebase library because of load time.
exports.api = functions.https.onRequest(app);
here is the package.json file.
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "10"
},
"dependencies": {
"busboy": "^0.3.1",
"cors": "^2.8.5",
"express": "^4.17.1",
"firebase": "^7.21.1",
"firebase-admin": "^8.9.0",
"firebase-functions": "^3.11.0",
"firebase-tools": "^7.11.0"
},
"devDependencies": {
"firebase-functions-test": "^0.1.6",
"node-gyp": "^5.0.7"
},
"private": true
}
With the backend i am fixing up, I use this sort of workthrough (if it helps!), which I am replacing with firebase stuff above - if that makes sense. It works currently, is accessible for signup and login functionality, the key part for me is just using firebase and firestore with it.
const config = require('../../config');
const express = require('express');
const app = express();
const server = require('http').createServer(app);
.....
server.listen(config.serverParams.port, config.serverParams.address, () => {
console.log(`Server running at http://${server.address().address}:${server.address().port}`);
});
....
app.use((req,res,next)=>{
//can reaplce * with website we want to allow access
res.header('Access-Control-Allow-Origin', 'https://moodmap.app/stats');
next();
});
....
io.on('connection', socket => {
socket.use((packet, next) => {
.....
});
});
Really appreciate any guidance on this matter!
Firebase and firestore seems like a nice way to avoid reinventing the wheel, if only i could simply type npm start, and begin testing with postman the underlying functionality :s
The API is based off another side project i did, which is largely here for those interested in exploring it more.
https://github.com/Hewlbern/LightSpeed/tree/master/sigops
Very happy to just use the easiest way forward - I don't want to have any of the libraries on the client side though, as i want to make my front end super efficient. Thanks!
Cloud Functions does not allow you to listen on any ports in order to receive requests. It automatically handles incoming connections using the URL automatically provided during deployment. All incoming requests to that URL are routed to your function callback defined by the exported function built by functions.https.onRequest().
If you require the ability to listen to specific ports, Cloud Functions is not the right product for your use case. Cloud Functions can not be deployed on custom servers - it only works on infrastructure provided by Google.
I am trying to run a server doing HTTP and Websocket requests. The HTTP requests will be used to pull data from a local file called “geo-data.json”, which I need to show a map of NYC using D3.js and the topojson library.
I am using Node.js to run the server. My package.json file looks like this:
{
"name": "inputusername_showlistofusers",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"node-geocoder": "^3.27.0",
"ws": "^7.3.0"
}
}
My Websocket configuration on the client side looks like this:
var wsUri = "ws://localhost:8080";
var ws = new WebSocket(wsUri);
ws.onopen = function (evt) {
console.log("1. Connected (from Client)")
}
ws.onmessage = function (message) {
var messageArray = JSON.parse(message.data)
console.log("5. This is the list of user names:", messageArray)
}
Also, on the client side I am including my D3 code to read the geo data from the “geo-data.json” and the “nyc-streets.json” files by using D3.json :
d3.json('geo-data.json', function (error, data) {
//code
}
d3.json('nyc-streets.json', function (error, data) {
//code
}
On the server side, I am using Websockets to broadcast a message. The code looks like this:
var WebSocket = require("ws");
wss = new WebSocket.Server({ port: 8080 });
wss.on("connection", function connection(ws) {
console.log("2. Connected (from Server)")
ws.on("message", function incoming(message) {
console.log("3. received from the client ", message);
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
console.log("7. All names connected: ", message)
}
})
})
})
The problem: when I run server.js using “node server.js”, the code (both on client and server) that requires Websocket runs smoothly, but the one that requires https does not:
d3.json('geo-data.json', function (error, data)
and I get the following error (I am opening the index.html file using File/Users…):
Access to XMLHttpRequest at 'file:///Users..." from origin 'null' has been blocked by CORS
policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
So I tried to fix this using http-server to run the server and also by making the following change on package.json : “scripts”:{start: “index.html”} However, now the Websocket code does not work, and I get the following error:
(index):112 WebSocket connection to 'ws://localhost:8080/' failed: Error during WebSocket handshake: Unexpected response code: 200
How can I make both HTTP and Websocket requests at the same time?
Thanks in advance.
Gonna post an overwhelming amount of info here and then I'll summarize at the end. Ultimately, Web Sockets is probably not the tool that you want to use. Likely, web sockets is working but it has a subtle difference from what you were expecting. Web Sockets are great for constant communication. A messenger or chat is a great use for this as you will constantly be checking for new messages.
I suggest using express instead. Here is a simple file that should get you started: https://www.tutorialsteacher.com/nodejs/serving-static-files-in-nodejs . It serves all the files that are in a public folder. You may want to move your files into such a folder.
The big issue is that the function d3.json( does not connect to your sockets. It looks up the file locally (which is possible but ultimately results in the CORS security error). That function is responsible for retrieving data. If you called the correct url d3.json('localhost:8080/geo-data.json under the new Express app, then you should have success. You will not need to do anything for network connections on the client side (no websockets or express on your client)
I created a simple javascript app with mojs, an animation library, and wanted to deploy it to heroku. First, I tried to "heroku create" etc and deploy the original app to heroku - the app was accessible, but the script didn't work. Second, I tried to change an app that I made following the Node.js tutorial from heroku website, by inserting a script bootstrap tag
<script src="http://cdn.jsdelivr.net/mojs/latest/mo.min.js"></script>
<script src="myburstscript.js"></script>
copying the script I made to the folder of this app
var myBurst = new mojs.Burst({
count:10,
radius: {0 :150},
angle: {0 : 180},
children : {
//fill:{'red' : 'blue'},
fill: ['red', 'purple', 'blue', 'violet'],
duration : 1000,
radius: 10,
shape: 'polygon',
delay: 'stagger(50)'
}
});
document.addEventListener('click', function(e) {
myBurst.replay();
});
then running "npm install mojs", and, as usual,
git add .
git commit -m "somedumbsh*t"
git push heroku master
But it didn't play the animation it plays on my localhost. Logs show no errors. The rest, the html part of the page, works fine. Why?
Heroku needs some server, not only the client-side code.
If you cannot start your app with:
PORT=4446 npm start
and then access it on:
http://localhost:4446/
then you won't be able to host it on Heroku.
(I'm assuming that you're using Node as indicated by the tags in your question.) It's important that your app needs to actually use the port number provided in the PORT environment variable, not just a hardcoded port number.
For example if you put all your static files (HTML, client-site JavaScript, CSS, images etc.) in a directory called html then you can use a simple server like this, e.g. called server.js:
'use strict';
const path = require('path');
const express = require('express');
const app = express();
const port = process.env.PORT || 3338;
app.use('/', express.static(path.join(__dirname, 'html')));
app.listen(port, () => console.log(`Listening on http://localhost:${port}/`));
Example package.json:
{
"name": "your-name",
"version": "0.0.0",
"description": "Your description",
"scripts": {
"start": "node server.js",
},
"author": "Your details",
"license": "MIT",
"dependencies": {
"express": "^4.15.2",
}
}
See also this example that I posted on GitHub for a complete solution that even has a Deploy to Heroku button:
https://github.com/rsp/node-live-color
It is an example that was created for this answer:
Getting data from/writing data to localhost with Express
where you can find more details on why it was written like that.
I'm trying just to practice manipulating data from a static html page to Node.Js without using Angular, React or other framework libraries. I've installed all of my dependencies and node is serving my index.html, however, I can't seem to get my post method to work. I can't get it to even console.log anything. I've looked at the other stack overflow issues and the documents but can't seem to figure out the issue.
Here is my code and file structure.
HTML:
<form id="citySearchForm" action="http://127.0.0.1:3000/getcity" method="POST">
<div>
<p>Choose a city:</p>
<input type="text" placeholder="Enter a city" id="getCitiesInput" name="city"></input>
<input type="submit" value="submit">
</div>
<div id="weather"></div>
<p><span id="temp"></span></p>
<p><span id="wind"></span></p>
</form>
node.js:
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var urlencodedParser = bodyParser.urlencoded({ extended: false});
app.use(express.static('public'));
app.get('/index.html', function(req, res){
res.sendFile(_dirname + "/" + "index.html");
})
app.post('/getcity', urlencodedParser, function(req, res){
response = { city : req.body.city };
console.log(response);
res.end(JSON.stringify(response));
})
app.listen(3000, function() { console.log('listening')});
json:
{
"name": "basic_weather_api",
"version": "1.0.0",
"description": "Just a basic api call",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.16.0",
"express": "^4.14.1"
}
}
I have verified your code and it works perfectly fine.
On submit with city = Test, I see this Json response in browser : {"city":"Test"}
Also, I am albe to see your log statement printed. But remember your console.log() statement is part of the node.js server code, which means your log statement will be printed in server log and not in the browser console. If you are running your node server from terminal using node server.js you will see your log printed in the terminal.
If you have modified version of dependencies multiple time, delete node_modules directory and try to run npm install again.