Feathers Authentication via POST works but socket doesn't - node.js

Here is a repo showing my latest progress, and here is my configuration. As it stands that repo now doesn't even authenticate with REST - although I think something is wrong with socket auth that needs to be looked at.
I configured feathers, was able to create a user REST-fully with Postman, and even get an auth token (I can post to /authenticate to get a token, and then verify that token - yay postman! yay REST api!).
But in the browser the story ain't so happy. I can use find to get data back, but authenticate just gives me errors.
In my googling I found this post and updated my client javascript to be this. I have also tried doing jwt auth with the token from postman, but that gives the same Missing Credentials error. Halp!
Code incoming...
app.js (only the configuration part to show order)
app.configure(configuration(path.join(__dirname, '..')))
.use(cors())
.use(helmet()) // best security practices
.use(compress())
.use(favicon(path.join(app.get('public'), 'favicon.ico')))
.use('/', feathers.static(app.get('public')))
.configure(socketio())
.configure(rest())
.configure(hooks())
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true }))
.configure(services) // pull in all services from services/index.js
.configure(middleware) // middleware from middleware/index.js
.hooks(appHooks)
Within services, I first add authentication, which is in its own file and that looks like this
authentication.js
const authentication = require('feathers-authentication');
const jwt = require('feathers-authentication-jwt');
const local = require('feathers-authentication-local');
const authManagement = require('feathers-authentication-management');
module.exports = function () {
const app = this;
const config = app.get('authentication');
// Set up authentication with the secret
app.configure(authentication(config));
app.configure(authManagement(config));
app.configure(jwt());
app.configure(local(config.local));
// The `authentication` service is used to create a JWT.
// The before `create` hook registers strategies that can be used
// to create a new valid JWT (e.g. local or oauth2)
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies)
],
remove: [
authentication.hooks.authenticate('jwt')
]
}
});
};
index.html (mostly stripped, just showing relevant script)
let url = location.protocol + '//' + location.hostname +
(location.port
? ':' + location.port
: '');
const socket = io(url);
const feathersClient = feathers()
.configure(feathers.socketio(socket))
.configure(feathers.hooks())
.configure(feathers.authentication({ storage: window.localStorage }));
Here's a screen shot showing some requests in chrome debugger and postman.

When default.json is set to use 'username' as the usernameField it outputs my Windows username, 'Matt'. This is because feathers-configuration checks to see if a value is a part of the OS environment.
https://github.com/feathersjs/feathers-configuration/blob/master/src/index.js#L26
Workaround
Manually set this configuration in authentication.js, for example:
// Set up authentication with the secret
const localConfig = {
'entity': 'users',
'service': 'users',
'usernameField': 'username',
'passwordField': 'password'
};
app.configure(authentication(config));
app.configure(local(localConfig));

Related

Serve multiple protected static folders using Express

I'm trying to write a very simple Express app, which is supposed to serve multiple static folders.
I have a root folder "stories" which contains multiple folders (story-1, story2, etc...). Each story folder contains static assets (scripts, CSS stylesheets, subpages...).
My users can unlock each of those stories, so each story folder must be protected. (If anyone tries to access http://backend/stories/story-1, it should give a 401 Forbidden).
My initial thought was to generate a one-time JWT upfront (like a signed url; not a bearer), add it to query params like http://backend/stories/story-1?jwt=the-jwt-token, then do some backend logic to test if the user has access to this content before serving it.
I tried fiddling with a basic express configuration + a custom authorization middleware :
Project structure :
...
-- /src
-- /stories ⬅️ custom public folder
-- /story-1 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
-- /story-2 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
-- /story-3 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
etc...
index.js :
const express = require("express");
const { authorized } = require("./middlewares/authorized");
const app = express();
const port = 3000;
app.use("/stories/:story", authorized);
app.use("/stories", express.static(__dirname + "/stories"));
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
authorized.js :
exports.authorized = (req, res, next) => {
const jwt = req.query.jwt;
if (!jwt) return res.sendStatus(401);
// todo : custom logic to test if the user has access to this content, if yes we do next(), if no we return a 401.
return next();
};
This simple example works partially, when I try to go to http://localhost:3000/stories/first-story (without JWT), I get a 401 (that's ok).
But when I add the jwt :
http://localhost:3000/stories/first-story/?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The middleware runs for every assets that are linked in the index.html, but those assets urls don't have the JWT query params, which leads to a 401.
I guess it's totally normal because that's how middlewares are intended to work. My guess is that i'm configuring express router wrong :
app.use("/stories/:story", authorized);
app.use("/stories", express.static(__dirname + "/stories"));
I would like to run the middleware only once, when any of the /:story subfolders inside /stories are asked to be served.
You write:
I would like to run the middleware only once, when any of the /:story subfolders inside /stories are asked to be served.
But if every .html (sub-)page is served by a separate HTTP request, each of these requests must be protected, assuming that the HTML contains material that is worthy of protection. (The styles and scripts may not need this extra protection.)
Therefore it is OK that the authorized middleware runs for each such request. And if the JWT was in a cookie (as suggested by Kaneki21), it would be present automatically in each request.
I would separate out the access control logic from the identity logic. You can use your jwt to verify that the user is who they say the are, and then use your existing knowledge of who that user is to grant them access.
I put a simple example using cookie-backed sessions below, note that you can add sequential middleware a, b, and c all in one function via app.use('/foobar',a,b,c).
// other setup
...
const session = require('express-session'),
fs = require('fs'),
{ Router } = require('express');
const secret = (() => {
let secretFile = '/path/to/my/secret.txt';
try {
// try reading a saved secret
return fs.readFileSync(secretFile,'utf8');
}
catch(err) {
// otherwise generate secret and save it
let random = require('crypto').randomBytes(128).toString('base64');
fs.writeFileSync(secretFile,random);
return random;
}
})();
// Add the session middleware to the app
app.use(session(
{ secret,
name: 'stories-and-whatnot',
cookie: { sameSite: true } }
));
// Create a router for stories and add it to the app
let storyRouter = Router();
app.use('/stories', storyRouter);
// add identity middleware to storyRouter
storyRouter.use( authorized);
let storyMax = 10;
for(let i=0; i<storyMax; i++) {
// set up the individual story routers
storyRouter.use(
`/story-${i}`,
function(req,res,next) {
if(!req.session.storyAccess || !req.session.storyAccess[i]) {
// If the user's session doesn't show it has access, reject with 401
res.status(401).end(`You do not have access to story ${i}`);
}
else {
// Otherwise let them proceed to the static router
next();
}
},
express.static(require('path').join(__dirname,`stories/story-${i}`)
);
}
...
// And somewhere else you have something like this
app.get('/access-granted', authorized, function(req,res,next) {
let { id } = req.query;
if(!req.session.storyAccess)
req.session.storyAccess = {};
req.session.storyAccess[id] = true;
res.end(`Access granted to story ${id}`);
});
You might consider, not using a middleware at all for the serving of content. But rather, to setup the user's set of "approved" paths.
That way a user, failing authentication, would have no valid paths, other then perhaps a preset collection of "base" paths.
This way, later after your authentication middleware the "routing" can be constrained to just that users set of "granted" paths.
Essentially model the access using sessions, which are established on first request, and then updated and maintained as things progress.
One solution is that you check if the user has the right to view the page on the client side. You'll need some JavaScript on the client side to do this.
You can store the token in LocalStorage after login. Then, at the beginning of the protected HTML file, you include your JS code to retrieve the token, and send a request to the server to check if the user is authenticated or not. Then, based on the response of the server you show the content or hide it.
To be honest, I rarely see the JWT in the URL. People talk about it here, here, here... You should revise your current approach carefully.

Making a Twitter Bot and keep getting Error Code 453

I'm trying to make a Twitter bot and using this article as my guide. I have:
Signed up for a Twitter Developer account and created an App
Changed the User authentication settings to the following:
First screen of user authentication settings
Second screen of user authentication settings
(Having installed Node and Twit) I created a server.js file with the following contents:
const Twit = require( 'twit' ),
fs = require( 'fs' ),
path = require( 'path' ),
config = require( path.join( __dirname, 'config.js' ) );
const T = new Twit( config );
T.post( 'statuses/update', { status: 'Look, I am tweeting!' }, function( err, data, response ) {
console.log( data )
} );
And a config.js file that looks like this (subbing out my codes with "XXX"):
const config = {
consumer_key: 'XXX',
consumer_secret: 'XXX',
access_token: 'XXX',
access_token_secret: 'XXX'
}
module.exports = config;
Finally, I ran my code with node server.js
What I got in response was the following error (and nothing was posted to my Twitter account):
{
errors: [
{
message: 'You currently have Essential access which includes access to Twitter API v2 endpoints only. If you need access to this endpoint, you’ll need to apply for Elevated access via the Developer Portal. You can learn more here: https://developer.twitter.com/en/docs/twitter-api/getting-started/about-twitter-api#v2-access-leve',
code: 453
}
]
}
My understanding was that I do not need Elevated access to create a Bot like this. Am I wrong about that? Could it be something with my User authentication settings? Do I somehow need to include a Client ID?
Any advice is much appreciated!
Twit uses api v1.1. Currently you have Essential Access which only gives you access to api v2. You can either apply for Elevated Access to use v1.1 or use another npm package for v2 like this one-
https://www.npmjs.com/package/twitter-api-v2

Homey Authorization

I've been doing a lot of research and testing for a couple of days now and decided to come here to ask instead since I, to be honest, have absolutely no idea how to do this. I've been able to code for quite some time now but I decided to try to connect to homey using node.js. I've been following tutorials, on different sites but I can't get it to work. My problem is trying to connect to homey. My current code looks like this:
const AthomCloudAPI = require('homey-api/lib/AthomCloudAPI');
async function myhome(){
// Create a Cloud API instance
const cloudApi = new AthomCloudAPI({
clientId: 'MyId',
clientSecret: 'MySecret',
});
// Get the logged in user
const user = await cloudApi.getAuthenticatedUser();
// Get the first Homey of the logged in user
const homeyApi = await user.getFirstHomey();
// Create a session on this Homey
const homey = await homey.authenticate();
}
myhome();
The error I'm getting is:
APIError: The access token was not found
I understand that I need to authenticate but that's the part I'm having problems with.
How should I do this?
The Homey Web API documentation contains a working example and API credentials that you can use to test your project locally. https://api.developer.homey.app/
const AthomCloudAPI = require('homey-api/lib/AthomCloudAPI');
// Create a Cloud API instance
const cloudApi = new AthomCloudAPI({
clientId: '5a8d4ca6eb9f7a2c9d6ccf6d',
clientSecret: 'e3ace394af9f615857ceaa61b053f966ddcfb12a',
});
If you try to run that does it work?

How to integrate OIDC Provider in Node jS

I tried to Integrate OIDC Provider to Node JS and I have a Sample Code. So, I run this Sample code it's throwing an error(unrecognized route or not allowed method (GET on /api/v1/.well-known/openid-configuration)).The problem is Issuer(https://localhost:3000) this Issuer is working fine. but i will change this Issuer((https://localhost:3000/api/v1/)) it's not working How to fix this Issue and I facing another issue also when I implement oldc-provider in node js. They Routes are override how to fix this issue
Sample.js
const { Provider } = require('oidc-provider');
const configuration = {
// ... see available options /docs
clients: [{
client_id: 'foo',
client_secret: 'bar',
redirect_uris: ['http://localhost:3000/api/v1/'],
true_provider: "pcc"
// + other client properties
}],
};
const oidc = new Provider('http://localhost:3000/api/v1/', configuration);
// express/nodejs style application callback (req, res, next) for use with express apps, see /examples/express.js
oidc.callback()
// or just expose a server standalone, see /examples/standalone.js
const server = oidc.listen(3000, () => {
console.log('oidc-provider listening on port 3000, check http://localhost:3000/api/v1/.well-known/openid-configuration');
});
Error
Defining Issuer Identifier with a path component does not affect anything route-wise.
You have two options, either mount the provider to a path (see docs), or define the actual paths you want for each endpoint to be prefixed (see docs).
I think you're looking for a way to mount, so the first one.

per-request session in meteor server?

I am adding an auth layer and I think I have it figured out except for one tricky detail.
My Meteor app doesn't have any routes but I've added a hook into the connect middleware so that the "/" route errors if there isn't a correct API token. If the token is okay then I call next() to forward the route to Meteor.
The problem is that, depending on the token, I need to set server-side parameters for the connection, and I don't know how to do this. For example, say I have a static list of API keys mapped to permission levels. If a user sends a request with "ADMIN_API_KEY" then I would like to set Session.permission_level = "admin" for use by the Meteor server's functions. Session is just for the client in Meteor, though.
# this code's in coffeescript
WebApp.connectHandlers.use '/', (req, res, next) ->
validator = new RequestValidator(req, next)
validations = [
"valid_namespace",
"only_https"
]
error = validator.validate(validations)
next(error)
# <<<<<<<<<<<<<<<<<<<<<<<<
# Here I want to set some config option which can be
# read by the server in the same way it can read things like
# Meteor.user()
In Rails I would just say session[:permission_level] = "admin". But it seems to not work this way in Meteor.
By the way, I am not using a Routing package yet in Meteor, though if that would make this easier than I would.
I'm not sure about Session I've been doing something like
import { DDP } from 'meteor/ddp';
import { DDPCommon } from 'meteor/ddp-common';
export const authMiddleware = (req, res, next) => {
const userId = identifyUser(req); // parse the request to get the token you expect
if (!userId) {
return next();
}
DDP._CurrentInvocation.withValue(new DDPCommon.MethodInvocation({
isSimulation: false,
userId,
}), () => {
next();
// in that context, Meteor.userId corresponds to userId
});
};
for my REST api and that works well regarding the user Id and being able to call Meteor function that should be invoke in a DDP context, like Users.find(...).

Resources