How to login a user on a REST API using restful-keystone - node.js

I am trying to expose a model created with keystonejs over a REST API using restful-keystone. The API should allow to create a new user or to retrieve the information of a user. This is my code
var beforeRetrieve = function (req, res,next) {
console.log(req.user);
next();
};
restful.expose({
User: {
show : ["_id","name", "email", ],
methods: ["retrieve", "create"]
}
}).before({
User: {
retrieve: [beforeRetrieve, middleware.requireUser],
create: [],
}
}).start();
The code is working but I am getting confused. console.log(req.user); is printing undefined which is logic. But how can I make it work and print the user initiating the request? Shall I include the user cookies in the request? Shall I create another API that allow the user to login and get the cookies/token? Is there already one in keystone? Can you please give me some idea on how to achieve that? My ideal case is to allow anyone to create a new user. By creating the new user a token should be returned and used in the future to identify the user. I am not sure how shall I proceed, can you please help me with some ideas, I am really confused

Please kindly check if you are using Keystone v4.0 but restful-keystone is supporting Keystone v0.3. For Keystone you can expose REST API without any additional modules.
For example you can edit routes/index.js to add a route:
// Setup Route Bindings
exports = module.exports = function (app) {
// Views
app.get('/', routes.views.index);
// API
app.get('/api/user', routes.api.user.get);
};
And then create route/api/user.js to capture the REST reqeust:
var keystone = require('keystone');
var cdf = keystone.list('User');
exports.get = function(req, res) {
...
}
But for user model, there is a ready-made REST API, but this requires Admin Permission:
http://server:port/keystone/api/users

Related

How to use only PART of the functionality in nested routes NodeJS

So I have a router schoolsRouter where all the school-specific functionality is being handled { login school, adding a new teacher, ...etc.). And I want the admin of the app to be able to add and delete new schools. Now the pattern I'm using encapsulates all the routing functionality in one file schools.routes.js where the School model is exposed. So the createSchool and deleteSchool routes are in the schools.routes.js but I need only the admin to be able to perform those operations and that seems pretty easy with merged routes like this (in admins.routes.js):
adminsRouter.use('/schools/', schoolsRouter);
but the problem is that now the admin can access all the other routes in schools.routes.js like schools/login which is something that I don't want to happen. So how can I make the adminsRouter use the create and delete operations from the schoolsRotuer without being able to access all these other functionalities? (Keeping in mind I'm using JWT authentication).
You could use middlewares in the routes that you wish to controll.
This is the middleware that I will name of admin-middleware.js
module.exports = (req, res, next) => {
if (user.admin === true) {
return next();
} else {
return res.status(403).send('Unauthorized')
}
}
So, this is your route declaration at schools.routes.js
const adminMiddleware = require('../YOUR_FOLDERS/admin-middleware.js');
schools.delete('/:id', adminMiddleware, (req, res) => {
return res.send('ok');
});
If you wish disregard a route, you can use this validation at your code in the middleware.
if(req.originalUrl.includes('/schools/login'))
return next();
I hope that it works to you.

How to remove authentication for introspection query in Graphql

so may be this is very basic question so please bear with me. Let me explain what I am doing and what I really need.
EXPLANATION
I have created a graphql server by using ApolloGraphql (apollo-server-express npm module).
Here is the code snippet to give you an idea.
api.js
import express from 'express'
import rootSchema from './root-schema'
.... // some extra code
app = express.router()
app.use(jwtaAuthenticator) // --> this code authenticates Authorization header
.... // some more middleware's added
const graphQLServer = new ApolloServer({
schema: rootSchema, // --> this is root schema object
context: context => context,
introspection: true,
})
graphQLServer.applyMiddleware({ app, path: '/graphql' })
server.js
import http from 'http'
import express from 'express'
import apiRouter from './api' // --> the above file
const app = express()
app.use([some middlewares])
app.use('/', apiRouter)
....
....
export async function init () {
try {
const httpServer = http.createServer(app)
httpServer
.listen(PORT)
.on('error', (err) => { setTimeout(() => process.exit(1), 5000) })
} catch (err) {
setTimeout(() => process.exit(1), 5000)
}
console.log('Server started --- ', PORT)
}
export default app
index.js
require('babel-core')
require('babel-polyfill')
require = require('esm')(module/* , options */)
const server = require('./server.js') // --> the above file
server.init()
PROBLEM STATEMENT
I am using node index.js to start the app. So, the app is expecting Authorization header (JWT token) to be present all the times, even for the introspection query. But this is not what I want, I want that introspection query will be resolvable even without the token. So that anyone can see the documentation.
Please shed some light and please guide what is the best approach to do so. Happy coding :)
.startsWith('query Introspection') is insecure because any query can be named Introspection.
The better approach is to check the whole query.
First import graphql and prepare introspection query string:
const { parse, print, getIntrospectionQuery } = require('graphql');
// format introspection query same way as apollo tooling do
const introspectionQuery = print(parse(getIntrospectionQuery()));
Then in Apollo Server configuration check query:
context: ({ req }) => {
// allow introspection query
if (req.body.query === introspectionQuery) {
return {};
}
// continue
}
There's a ton of different ways to handle authorization in GraphQL, as illustrated in the docs:
Adding middleware for express (or some other framework like hapi or koa)
Checking for authorization inside individual resolvers
Checking for authorization inside your data models
Utilizing custom directives
Adding express middleware is great for preventing unauthorized access to your entire schema. If you want to allow unauthenticated access to some fields but not others, it's generally recommended you move your authorization logic from the framework layer to the GraphQL or data model layer using one of the methods above.
So finally I found the solution and here is what I did.
Let me first tell you that there were 2 middle-wares added on base path. Like this:
app //--> this is express.Router()
.use(jwtMw) // ---> these are middlewares
.use(otherMw)
The jwtMw is the one that checks the authentication of the user, and since even introspection query comes under this MW, it used to authenticate that as well. So, after some research I found this solution:
jwtMw.js
function addJWTMeta (req, res, next) {
// we can check for null OR undefined and all, then check for query Introspection, with better condition like with ignore case
if (req.body.query.trim().startsWith('query Introspection')) {
req.isIntrospection = true
return next()
}
...
...
// ---> extra code to do authentication of the USER based on the Authorization header
}
export default addJWTMeta
otherMw.js
function otherMw (req, res, next) {
if (req.isIntrospection) return next()
...
...
// ---> extra code to do some other context creation
}
export default otherMw
So here in jwtMw.js we are checking that if the query is Introspection just add a variable in req object and move forward, and in next middleware after the jwtMw.js whosoever wants to check for introspection query just check for that variable (isIntrospection, in this case) and if it is present and is true, please move on. We can add this code and scale to every middleware that if req.isIntrospection is there just carry on or do the actual processing otherwise.
Happy coding :)

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(...).

Node Express auth status

I have multiple routes, split into different files (my app consists of different "modules", which I maintain in separate folders. For each folder, there is an index.js file in which I manage the routes per module, and I require these in the app.js file).
For every route, I will require to check the auth, and pass the loggedIn status to the header of every page:
//Default variables for the ejs template
var options = {
loggedIn: true
};
res.render("home/home", options);
If the logged in status is true, then the user's name will be displayed. If not, the login / signup labels are displayed.
What is the best way to centralise this, so that I don't need to require the auth script in every of these index.js (route) files?
I need to be able to pass the auth status to the view via the options object (see example).
In your auth, module, use a middleware function. That function can check and store res.locals.loggedIn which will be available for any view that will eventually be rendered. Just make sure the app.use call executes prior to your other routes and it will work properly.
app.use(function auth(req, res, next) {
res.locals.loggedIn = true; // compute proper value here
next();
});
From what I understand you need to do this for every request.One common thing is adding this as middleware so that all the request gets this .
For Example :
var http = require('http');
var connect = require('connect');
var app = connect();
app.use(function(req, res) {
res.end('Hello!');
});
http.createServer(app).listen(3000)
Now for every request , Hello is printed . You could extract this as a module and reuse it across projects. Check here for more details

AngularJS and ExpressJS session management?

I would like to keep session across all the page. For this project, I am using expressJs, nodeJS as server side. AngularJS in front end.
I am not sure, how to handle session when view changes or url changes. Because I need to take care of both expressJS router or angularJs router.
What approach should I follow?
angularJS router
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/welcome', {templateUrl: 'partials/welcome.html', controller: 'MyCtrl2'});
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'MyCtrl2'});
$routeProvider.when('/signup', {templateUrl: 'partials/signup.html', controller: 'singupController'});
$routeProvider.otherwise({redirectTo: '/'});
}]);
Signup controller
myApp.controller('singupController',function($scope,$rootScope,$http){
$scope.doSingnup = function() {
var formData = {
'username' : this.username,
'password' : this.password,
'email' : null
};
var jdata = JSON.stringify(formData);
$http({method:'POST',url:'/signup',data:jdata})
.success(function(data,status,headers,config){
console.log(data);
}).
error(function(data,status,headers,config){
console.log(data)
});
}
})
ExpressJS router
module.exports = exports = function(app, db) {
var sessionHandler = new SessionHandler(db);
var contentHandler = new ContentHandler(db);
// Middleware to see if a user is logged in
app.use(sessionHandler.isLoggedInMiddleware);
app.get('/', contentHandler.displayMainPage);
app.post('/login', sessionHandler.handleLoginRequest);
app.get('/logout', sessionHandler.displayLogoutPage);
app.get("/welcome", sessionHandler.displayWelcomePage);
app.post('/signup', sessionHandler.handleSignup);
app.get('*', contentHandler.displayMainPage);
// Error handling middleware
app.use(ErrorHandler);
}
After signup, I would like to redirect to the login page. How can I do that in the above router. which one of the following should I use to change the view of app
1) $location of angularJS
2) redirect of ExpressJS
So i had the same problem and to be fair i might have read the approach somewhere i don't remember anymore.
Problem: Angular builds single page apps. After refresh, you loose scope and with it the authenticated user.
Approach
AngularJS modules offer a startup function called run which is called always when the page is loaded. Perfect for refresh/reload.
myApp.run(function ($rootScope, $location, myFactory) {
$http.get('/confirm-login')
.success(function (user) {
if (user && user.userId) {
$rootScope.user = user;
}
});
}
express-session saves the sessions for you and authenticates you with the sessionId your browser sends. So it always knows if you are authenticated or not.
router.get('/confirm-login', function (req, res) {
res.send(req.user)
}
);
All i had to do is, after refreshing and all dependencies were loaded, ask if i am authenticated and set $rootScope.user = authenticatedUserFromExpress;
There are two different concepts here - server side session state and the user state on the client side in Angular. In express you can use the session via req.session to manage session based data.
On the angular side, there is only scope in your controllers. If you want to keep track of some data across multiple controllers, you need to create a service to store the data in and inject the service into the controllers you need.
A typical lifecycle is to first check if there is data already in the service, if so use it. If not, wait for the data to be populated (by the user or app or whatever) then detect those changes and synchronize with your service.
signup controller
function SignupCtrl($scope, $http, $location) {
$scope.form = {}; // to capture data in form
$scope.errorMessage = ''; // to display error msg if have any
$scope.submitPost = function() { // this is to submit your form can't do on
//traditional way because it against angularjs SPA
$http.post('/signup', $scope.form).
success(function(data) { // if success then redirect to "/" status code 200
$location.path('/');
}).error(function(err) { // if error display error message status code 400
// the form can't be submitted until get the status code 200
$scope.errorMessage = err;
});
};
}
sessionHandler.handleSignup
this.handleSignup = function(req, res, next) {
"use strict";
// if you have a validate function pass the data from your
// Signup controller to the function in my case is validateSignup
// req.body is what you need
validateSignup(req.body, function(error, data) {
if(error) {
res.send(400, error.message); // if error send error message to angularjs
}else {
// do something else
// rmb to res.send(200)
}
});
}
validatesignup
function validateSignup(data,callback) {
"use strict"; // the data is req.body
//so now you can access your data on your form
// e.g you have 2 fields name="password" and name="confirmPassword on your form"
var pass = data.password,
comPass = data.confirmPassword;
if(pass != comPass){
callback(new Error('Password must match'), null);
// then show the error msg on the form by using
//angular ng-if like <div ng-if="errorMessage">{{errorMessage}}</div>
}else{
callback(null, data);
}
}
hope this help
Of all the answers here, I like #alknows's approach best. However, like the other answers that suggest you send a request to the server to get the current user data, there are a couple issues I take with them:
You have to deal with race conditions as a result of your AJAX ($http) call.
You're sending an unnecessary request to the server after it already rendered your index.html
I tried #alknow's approach and it worked out for me after I was able to resolve the many race conditions that came up as a result of my angular app controllers and config needing the current user to do their job. I try my best to avoid race conditions when appropriate, so I was a bit reluctant to continue with this approach. So I thought of a better approach: send the current user data down with your index.html and store it locally.
My Approach: Embed currentUser in index.html & store locally on client
In index.html on your server, make a script tag to hold whatever data you want to pass to the client:
```
<!--YOUR OTHER index.html stuff go above here-->
<script id="server-side-rendered-client-data" type="text/javascript">
var __ssr__CData = {
currentUser: { id: '12345', username: 'coolguy', etc: 'etc.' }
}
</script>
```
Then, as #alknows suggested, in app.js or wherever you initiate your angular app, add app.run(..., () => {...}). In app.run(), you will want to grab the server side rendered client data object, which I named obscurely __ssr_CData so that I am less likely to run into name collisions across the global namespace later in my other javascript:
var myAngularApp = angular.module("mainApp", ['ngRoute']);
myAngularApp.run(function ($rootScope) {
const currentUserFromServer = __ssr__CData.currentUser
const currentUserAccessTokenFromServer = __ssr__CData.accessToken
const currentUser =
CurrentUser.set(currentUserAccessTokenFromServer, currentUserFromServer)
$rootScope.currentUser = currentUser
});
As you know app.run() will be called whenever the page does a full reload. CurrentUser is a global class for managing my angular app's current user in the single page environment. So when I call CurrentUser.set(...) it stores the current user data in a place I can retrieve later in my angular app by calling CurrentUser.get(). So in any of your angular app controller's you can now retrieve the current user the server provided by simply doing this:
myAngularApp.controller('loginController',function($scope, $rootScope, $http){
//check if the user is already logged in:
var currentUser = CurrentUser.get()
if(currentUser) {
alert("HEY! You're already logged in as " +currentUser.username)
return $window.location.href = "/";
}
//there is no current user, so let user log in
//...
}
In that example, I made use of CurrentUser.get(), which I explained above, to get the previously stored current user from the server. I could have also retrieved that current user by accessing $rootScope.currentUser because I stored it there, too. It's up to you.
myAngularApp.controller('signupController',function($scope, $rootScope, $http){
//check if the user is already logged in:
var currentUser = CurrentUser.get()
if(currentUser) {
alert("HEY! You're already logged in as " +currentUser.username)
return $window.location.href = "/";
}
//there is no current user, so let user signup
//... you run your signup code after getting form data
$http({method:'POST',url:'/signup',data:jdata})
.success(function(data,status,headers,config){
//signup succeeded!
//set the current user locally just like in app.js
CurrentUser.set(data.newUser)
//send user to profile
return $window.location.href = "/profile";
})
.error(function(data,status,headers,config){
//something went wrong
console.log(data)
});
}
Now, after a new user has signed up, your server returned the new user from the AJAX call. We set that new user as the current user by calling CurrentUser.set(...) and send the user to their profile. You can now get the current user in the profile controller the same way you did to check if the current user existed in the login and signup controllers.
I hope this helps anyone who comes across this. For your reference, I'm using the client-sessions module to handle sessions on my server.

Resources