Update Contact Details using Azure AD Graph & Node - node.js

I am using Node.js and azure-graph to create a user in Azure. It works as expected for basic fields like Name, etc. However, jobTitle and other fields that are usually found in the portal cannot be updated. Any pointers?
let msRest = require('ms-rest-azure');
let azureGraph = require('azure-graph');
let tenantId = common.configDefaults.azure_tenant;
let clientId = common.configDefaults.azure_client_id;
let clientPwd = common.configDefaults.azure_client_secret;
let create_user_in_azure_ad = function (user, cb) {
msRest.loginWithServicePrincipalSecret(clientId, clientPwd, tenantId, {
tokenAudience: 'graph'
}, function (err, credentials, subscriptions) {
if (err) {
done(err.message, null);
} else {
// Create Azure Graph Client to access
let client = new azureGraph(credentials, tenantId);
let password = common.generatePassword(10);
let userParams = {
accountEnabled: true,
userPrincipalName: user.email_official, //please add your domain over here
displayName: user.display_name,
mailNickname: user.email_official.split("#")[0],
jobTitle: "A FANCY TITLE",
passwordProfile: {
password: password,
forceChangePasswordNextLogin: true
},
};
// Now, we can create the User in Active Directory
client.users.create(userParams, function (err, done) {
if (err) {
cb(err, null);
} else {
// The user is created now with a password. Return this information
cb(null, {user: user, password: password});
}
});
}
});
};

Though I'm not aware of Node.js, but I think you can create/update jobTitle by updating contact of a specific user.

Related

check MFA is enabled for a user using rest api

How do I check MFA is enabled for AD users using rest API loginWithServicePrincipalSecret
is there anyone who can help me out to do this....I want to do this using node sdk like this
require("isomorphic-fetch");
const { UserAgentApplication } = require("msal");
const { ImplicitMSALAuthenticationProvider } = require("#microsoft/microsoft-graph-client/lib/src/ImplicitMSALAuthenticationProvider");
const { MSALAuthenticationProviderOptions } = require("#microsoft/microsoft-graph-client/lib/src/MSALAuthenticationProviderOptions");
const msalConfig = {
auth: {
clientId: "bec52b71-dc94-4577-9f8d-b8536ed0e73d", // Client Id of the registered application
},
};
const graphScopes = ["user.read", "mail.send"]; // An array of graph scopes
const msalApplication = new UserAgentApplication(msalConfig);
const Options = new MSALAuthenticationProviderOptions(graphScopes);
const authProvider = new ImplicitMSALAuthenticationProvider(
msalApplication,
Options
);
const options = {
authProvider,
};
const Client = require("#microsoft/microsoft-graph-client");
const client = Client.init(options);
async function test() {
try {
let res = await client
.api("/reports/credentialUserRegistrationDetails")
.version("beta")
.get();
console.log("res: ", res);
} catch (error) {
throw error;
}
}
test();
This is possible with MS Graph API,
To Get information of users registered with MFA and hasn't, we can use isMfaRegistered property in credentialUserRegistrationDetails .
credentialUserRegistrationDetails help us to get the details of the
usage of self-service password reset and multi-factor authentication
(MFA) for all registered users. Details include user information,
status of registration, and the authentication method used.
This is possible programmatically with MS Graph where you will get a JSON reports an can be plugged into other reports or can be represented programmatically itself
Example:
GET https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails
sample output:
{
"id": "****************************",
"userPrincipalName": "NKS#nishantsingh.live",
"userDisplayName": "Nishant Singh",
"isRegistered": false,
"isEnabled": true,
"isCapable": false,
"isMfaRegistered": true,
"authMethods": [
"mobilePhone"
]
}
Sample code for your Node JS,
const options = {
authProvider,
};
const client = Client.init(options);
let res = await client.api('/reports/credentialUserRegistrationDetails')
.version('beta')
.get();
To implement your NodeJS code please go through step-by-step guide in MS Documentation

Why getting this "Authentication_MissingOrMalformed" error during Microsoft Graph API call?

const msRestAzure = require('ms-rest-azure');
const { GraphRbacManagementClient } = require('azure-graph');
module.exports = async function (context, req) {
try{
const credentials = await msRestAzure.loginWithServicePrincipalSecret(clientId, clientSecret, tanent);
const client = new GraphRbacManagementClient(credentials, tenantId);
const results = await client.users.list();
context.res = {
body: results
};
} catch (error) {
console.log('error==> ',error); // Getting error: Authentication_MissingOrMalformed
context.res = {
body: error
};
}
}
I want to get all users list using azure graph sdk. But after calling the client.users.list() function I'm getting the error ("Authentication_MissingOrMalformed"). How do I fix this error and get all users list.
How to get all users list from Azure Active Directory using Azure Graph SDK (Nodejs) ?
The main problem is missing { tokenAudience: 'graph' }, please refer to my code:
const msRestAzure = require('ms-rest-azure');
const { GraphRbacManagementClient } = require('azure-graph');
module.exports = async function (context, req) {
try{
msRestAzure.loginWithServicePrincipalSecret("clientId", "clientSecret", "tenantId", { tokenAudience: 'graph' }, function (err, credentials) {
if (err) return console.log(err);
const client = new GraphRbacManagementClient(credentials, "tenantId");
client.users.list((err, results, request, response) => {
if (err) return console.log(err);
console.log(JSON.parse(response.body).value.length);
});
});
} catch (error) {
console.log('error==> ',error);
context.res = {
body: error
};
}
}
After running the code above, if the number of users in your AD is greater than 100, it will output 100 because graph api can response 100 users in a page(default is 100).
==================================Update================================
Please check if you have added the permission to the application registered in Azure AD. If you didn't add the permission, please follow the below steps:
1. Go to the application which registered in your Azure AD (It's the application which you use its clientId).
2. Add the permission.
3. Click "Grant admin consent for xxx".
4. After a few minutes, run your code again.

Verify if a phone number exist in firebase app using firebase cloud function

I am new to the firebase (and all its features) space. I have read the documentation, and I have been able to use the web sdk properly. I have created a file where all my current firebase code is written as seen in firebaseApi.js below. Also, below is an example of how I have used the functions under registration.js (Kindly correct if I am doing it wrong), the sample works. I was trying to implement
admin.auth().getUserByPhoneNumber(phoneNumber),
which I want to use to check if a currently inputted phone number already exists in the App. But I have read the Admin SDKs cannot be used in client-side environments and should only be used in privileged server environments owned or managed by the developers of a Firebase app. I am kinda lost on how to go around this.
is it possible to connect firebase cloud functions to the client-side like
I am doing with the firebaseApi?
I have cleaned up the code and kept only the relevant parts
firebaseApi.js
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
import 'firebase/database';
import 'firebase/storage';
const config = {config};
firebase.initializeApp(config);
class Firebase {
register = ({ fullname, email, phone }) => {
const user = Firebase.auth.currentUser.uid;
const firestoreRef = Firebase.firestore.collection('Users').doc(user);
const settings = {
fullname,
email,
phone,
};
firestoreRef
.set(settings);
};
static init() {
Firebase.auth = firebase.auth();
Firebase.firestore = firebase.firestore();
Firebase.database = firebase.database();
Firebase.storage = firebase.storage();
Firebase.email = firebase.auth.EmailAuthProvider;
Firebase.google = firebase.auth.GoogleAuthProvider;
Firebase.phoneVerify = new firebase.auth.PhoneAuthProvider();
Firebase.phone = firebase.auth.PhoneAuthProvider;
}
}
Firebase.shared = new Firebase();
export default Firebase;
registration.js
import Firebase from './firebaseApi';
onCompleteReg() {
const { fullname, email, email } = this.state;
const settings = {
fullname,
email,
email
};
Firebase.shared
.registerSettings(settings)
.then(() => {
console.log('Successful');
}).catch((e) => {
console.log(e);
})
}
As a matter of privacy and best practices, unless the current user is an administrator, I would not be exposing the ability to check if any given phone number is used by any individual and/or is tied to your application.
Wrapped in Cloud Function
As the Admin SDK is to be used only from secure environments, you can only expose it's functionality by way of some API. It is beneficial in this case to handle user authentication and CORS automatically, so I'll use a Callable Function. Based on the sensitive nature of such an API, it would also be advised to rate-limit access to it which can be easily achieved using the firebase-functions-rate-limiter package. In the below code, we limit the API calls to 2 uses per user and 10 uses across all users, per 15 second period to prevent abuse.
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import { FirebaseFunctionsRateLimiter } from 'firebase-functions-rate-limiter';
admin.initializeApp();
const realtimeDb = admin.database();
const perUserLimiter = FirebaseFunctionsRateLimiter.withRealtimeDbBackend(
{
name: 'rate-limit-phone-check',
maxCalls: 2,
periodSeconds: 15,
},
realtimeDb
);
const globalLimiter = FirebaseFunctionsRateLimiter.withRealtimeDbBackend(
{
name: 'rate-limit-phone-check',
maxCalls: 10,
periodSeconds: 15,
},
realtimeDb
);
exports.phoneNumber = functions.https.onCall(async (data, context) => {
// assert required params
if (!data.phoneNumber) {
throw new functions.https.HttpsError(
'invalid-argument',
'Value for "phoneNumber" is required.'
);
} else if (!context.auth || !context.auth.uid) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.'
);
}
// rate limiter
const [userLimitExceeded, globalLimitExceeded] = await Promise.all(
perUserLimiter.isQuotaExceededOrRecordUsage('u_' + context.auth.uid),
globalLimiter.isQuotaExceededOrRecordUsage('global'));
if (userLimitExceeded || globalLimitExceeded) {
throw new functions.https.HttpsError(
'resource-exhausted',
'Call quota exceeded. Try again later',
);
}
let userRecord = await admin.auth.getUserByPhoneNumber(phoneNumber);
return userRecord.uid;
}
To call the check, you would use the following code on the client:
let checkPhoneNumber = firebase.functions().httpsCallable('phoneNumber');
checkPhoneNumber({phoneNumber: "61123456789"})
.then(function (result) {
let userId = result.data;
// do something with userId
})
.catch(function (error) {
console.error('Failed to check phone number: ', error)
});
Attempt by Login
Rather than allow users to find out if a phone number exists or specifically exists on your service, it is best to follow the Phone Number authentication flow and allow them to prove that they own a given phone number. As the user can't verify more than one number en-masse, this is the safest approach.
From the Firebase Phone Auth Reference, the following code is used to verify a phone number:
// 'recaptcha-container' is the ID of an element in the DOM.
var applicationVerifier = new firebase.auth.RecaptchaVerifier(
'recaptcha-container');
var provider = new firebase.auth.PhoneAuthProvider();
provider.verifyPhoneNumber('+16505550101', applicationVerifier)
.then(function(verificationId) {
var verificationCode = window.prompt('Please enter the verification ' +
'code that was sent to your mobile device.');
return firebase.auth.PhoneAuthProvider.credential(verificationId,
verificationCode);
})
.then(function(phoneCredential) {
return firebase.auth().signInWithCredential(phoneCredential);
});
Privileged Phone Search
If you want an appropriately privileged user (whether they have an administrator or management role) to be able to query users by a phone number, you can use the following scaffolding. In these code samples, I limit access to those who have the isAdmin claim on their authentication token.
Database structure: (see this answer for more info)
"phoneNumbers": {
"c011234567890": { // with CC for US
"userId1": true
},
"c611234567890": { // with CC for AU
"userId3": true
},
...
}
Database rules:
{
"rules": {
...,
"phoneNumbers": {
"$phoneNumber": {
"$userId": {
".write": "auth.uid === $userId && (!newData.exists() || root.child('users').child(auth.uid).child('phoneNumber').val() == ($phoneNumber).replace('c', ''))" // only this user can edit their own record and only if it is their phone number or they are deleting this record
}
},
".read": "auth != null && auth.token.isAdmin == true", // admins may read/write everything under /phoneNumbers
".write": "auth != null && auth.token.isAdmin == true"
}
}
}
Helper functions:
function doesPhoneNumberExist(phoneNumber) {
return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).once('value')
.then((snapshot) => snapshot.exists());
}
// usage: let exists = await doesPhoneNumberExist("611234567890")
function getUsersByPhoneNumber(phoneNumber) {
return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).once('value')
.then((snapshot) => snapshot.exists() ? Object.keys(snapshot.val()) : []);
}
// usage: let usersArray = await getUsersByPhoneNumber("611234567890") - normally only one user
function searchPhoneNumbersThatStartWith(str) {
if (!str || str.length < 5) return Promise.reject(new Error('Search string is too short'));
return firebase.database.ref("phoneNumbers").startAt("c" + str).endAt("c" + str + "\uf8ff").once('value')
.then((snapshot) => {
let phoneNumbers = [];
snapshot.forEach((phoneEntrySnapshot) => phoneNumbers.push(phoneEntrySnapshot.key));
return phoneNumbers;
});
}
// usage: let matches = await searchPhoneNumbersThatStartWith("61455")
// best handled by Cloud Function not client
function linkPhoneNumberWithUser(phoneNumber, userId) {
return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).child(userId).set(true);
}
// usage: linkPhoneNumberWithUser("611234567890", firebase.auth().currentUser.uid)
// best handled by Cloud Function not client
function unlinkPhoneNumberWithUser(phoneNumber, userId) {
return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).child(userId).remove();
}
// usage: unlinkPhoneNumberWithUser("611234567890", firebase.auth().currentUser.uid)

Password reset is not working in loopback 3.0

I've been trying to implement the reset password functionality in my project which uses nodejs and loopback version 3.0 . Loopback provides in built method for this reset password functionality in the user.js.
When I run the project and and test the reset password it runs without giving any errors but the email is not received.
This is the inbuilt method given by loopback for password reset functionality.
User.resetPassword = function(options, cb) {
// console.log("options : "+options);
// console.log("cb : "+cb);
cb = cb || utils.createPromiseCallback();
var UserModel = this;
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
options = options || {};
if (typeof options.email !== 'string') {
var err = new Error(g.f('Email is required'));
err.statusCode = 400;
err.code = 'EMAIL_REQUIRED';
cb(err);
return cb.promise;
}
try {
if (options.password) {
UserModel.validatePassword(options.password);
}
} catch (err) {
return cb(err);
}
var where = {
email: options.email,
};
if (options.realm) {
where.realm = options.realm;
}
UserModel.findOne({where: where}, function(err, user) {
if (err) {
return cb(err);
}
if (!user) {
err = new Error(g.f('Email not found'));
err.statusCode = 404;
err.code = 'EMAIL_NOT_FOUND';
return cb(err);
}
// create a short lived access token for temp login to change password
// TODO(ritch) - eventually this should only allow password change
if (UserModel.settings.emailVerificationRequired && !user.emailVerified) {
err = new Error(g.f('Email has not been verified'));
err.statusCode = 401;
err.code = 'RESET_FAILED_EMAIL_NOT_VERIFIED';
return cb(err);
}
if (UserModel.settings.restrictResetPasswordTokenScope) {
const tokenData = {
ttl: ttl,
scopes: ['reset-password'],
};
user.createAccessToken(tokenData, options, onTokenCreated);
} else {
// We need to preserve backwards-compatibility with
// user-supplied implementations of "createAccessToken"
// that may not support "options" argument (we have such
// examples in our test suite).
user.createAccessToken(ttl, onTokenCreated);
}
function onTokenCreated(err, accessToken) {
if (err) {
return cb(err);
}
cb();
UserModel.emit('resetPasswordRequest', {
email: options.email,
accessToken: accessToken,
user: user,
options: options,
}
);
}
});
return cb.promise;
};
When i enter the email from loopback api for password reset it gives no errors in the console but the email is not working.
The method resetPassword is called during the process.Console log inside the method is printed as shown below.
{ email: '**********#gmail.com',
authorizedRoles: { '$everyone': true } }
[Function: callback]
The thing which confuses me is that the verify email method is working
which is also comes inbuilt in the user.js .The following is printed in the console when the verification email is sent.
mx resolved: [ { exchange: 'alt1.gmail-smtp-in.l.google.com', priority: 10 },
{ exchange: 'alt2.gmail-smtp-in.l.google.com', priority: 20 },
{ exchange: 'gmail-smtp-in.l.google.com', priority: 5 },
{ exchange: 'alt4.gmail-smtp-in.l.google.com', priority: 40 },
{ exchange: 'alt3.gmail-smtp-in.l.google.com', priority: 30 } ]
MX connection created: alt1.gmail-smtp-in.l.google.com
recv gmail.com>220 mx.google.com ESMTP 1si9238203plw.390 - gsmtp
send gmail.com>EHLO gmail.com
recv gmail.com>250-mx.google.com at your service, [112.135.5.40]
recv gmail.com>250-SIZE 157286400
recv gmail.com>250-8BITMIME
recv gmail.com>250-STARTTLS
recv gmail.com>250-ENHANCEDSTATUSCODES
recv gmail.com>250-PIPELINING
recv gmail.com>250 SMTPUTF8
send gmail.com>MAIL FROM:<hasikasadaruwan.mgtuk#gmail.com>
recv gmail.com>452 (IP, Sender) first encounter.
It would be a great help if anyone help me to solve this problem, I've been stuck here for days.
THANKS in advance.
You must handle resetPasswordRequest endpoint in your extended model like this.
MyUser.on("resetPasswordRequest", function(info) {
console.log(info.email); // the email of the requested user
console.log(info.accessToken.id); // the temp access token to allow password reset
var url = "http://**********";
var html =
'Click <a href="' +
url +
"?access_token=" +
info.accessToken.id +
'">here</a> to reset your password.</br><h2>Link will be expired in 15 minutes.';
//'here' in above html is linked to : 'http://<host:port>/reset-password?access_token=<short-lived/temporary access token>'
MyUser.app.models.Email.send(
{
to: info.email,
from: senderAddress,
subject: "Password reset",
html: html
},
function(err) {
if (err) return console.log("> error sending password reset email");
console.log("> sending password reset email to:", info.email);
}
); });
Provide URL of your form. On that form submit use reset-password endpoint.
Checkout this reference: loopback-example-user-management
This worked for me. Thank you!

Express.js/Mongoose user roles and permissions

I am creating a fairly simple site with Node, Express and Mongoose. The site needs to have have user roles and permissions. My thoughts are that i'll validate permissions based on user interaction with the data base.
In mongoose is there a way to determine the type of CRUD operation currently being carried out possibly by a user?
I've found a solution. It would be great to hear peoples opinions on this.
I have a permissions config object which defines each role and their permissions.
Permissions config object
roles.admin = {
id: "admin",
name: "Admin",
description: "",
resource : [
{
id : 'blog',
permissions: ['create', 'read', 'update', 'delete']
},
{
id : 'user',
permissions: ['create', 'read', 'update', 'delete']
},
{
id : 'journal',
permissions: ['create', 'read', 'update', 'delete']
},
]
};
roles.editor = {
id: "editor",
name: "Editor",
description: "",
resource : [
{
id : 'blog',
permissions: ['create', 'read', 'update', 'delete']
},
{
id : 'user',
permissions: ['read']
},
{
id : 'journal',
permissions: ['create', 'read', 'update']
},
]
};
Middleware function
var roles = require('./config');
var permissions = (function () {
var getRoles = function (role) {
var rolesArr = [];
if (typeof role === 'object' && Array.isArray(role)) {
// Returns selected roles
for (var i = 0, len = role.length; i < len; i++) {
rolesArr.push(roles[role[i]]);
};
return rolesArr;
} else if (typeof role === 'string' || !role) {
// Returns all roles
if (!role) {
for (var role in roles) {
rolesArr.push(roles[role]);
};
}
// Returns single role
rolesArr.push(roles[role]);
return rolesArr;
}
},
check = function (action, resource, loginRequired) {
return function(req, res, next) {
var isAuth = req.isAuthenticated();
// If user is required to be logged in & isn't
if (loginRequired && !isAuth) {
return next(new Error("You must be logged in to view this area"));
}
if (isAuth || !loginRequired) {
var authRole = isAuth ? req.user.role : 'user',
role = get(authRole),
hasPermission = false;
(function () {
for (var i = 0, len = role[0].resource.length; i < len; i++){
if (role[0].resource[i].id === resource && role[0].resource[i].permissions.indexOf(action) !== -1) {
hasPermission = true;
return;
}
};
})();
if (hasPermission) {
next();
} else {
return next(new Error("You are trying to " + action + " a " + resource + " and do not have the correct permissions."));
}
}
}
}
return {
get : function (role) {
var roles = getRoles(role);
return roles;
},
check : function (action, resource, loginRequired) {
return check(action, resource, loginRequired);
}
}
})();
module.exports = permissions;
Then i created a middleware function, when the check method gets called it gets the users role from the req object (req.user.role). It then looks at the params passed to the middleware and cross references them with those in the permissions config object.
Route with middlware
app.get('/journal', `**permissions.check('read', 'journal')**`, function (req, res) {
// do stuff
};
This is my implementation. The code is reusable for client and server. I use it for my express/angular website
Reduce code duplicate, better consistence between client/server
Bonus benefit: on client's adapter, we can simply return true to grant max access to test the robustness of server (since hackers and easily overcome client side restrict )
in app/both/both.js
var accessList = {
//note: same name as controller's function name
assignEditor: 'assignEditor'
,adminPage: 'adminPage'
,editorPage: 'editorPage'
,profilePage: 'profilePage'
,createArticle: 'createArticle'
,updateArticle: 'updateArticle'
,deleteArticle: 'deleteArticle'
,undeleteArticle: 'undeleteArticle'
,banArticle: 'banArticle'
,unbanArticle: 'unbanArticle'
,createComment: 'createComment'
,updateComment: 'updateComment'
,deleteComment: 'deleteComment'
,undeleteComment: 'undeleteComment'
,banComment: 'banComment'
,unbanComment: 'unbanComment'
,updateProfile: 'updateProfile'
}
exports.accessList = accessList
var resourceList = {
//Note: same name as req.resource name
profile: 'profile'
,article: 'article'
,comment: 'comment'
}
exports.resourceList = resourceList
var roleList = {
admin: 'admin'
,editor: 'editor'
,entityCreator: 'entityCreator'
,profileOwner: 'profileOwner' //creator or profile owner
,normal: 'normal' //normal user, signed in
,visitor: 'visitor' //not signed in, not used, open pages are uncontrolled
}
var permissionList = {}
permissionList[accessList.assignEditor] = [roleList.admin]
permissionList[accessList.adminPage] = [roleList.admin]
permissionList[accessList.editorPage] = [roleList.admin, roleList.editor]
permissionList[accessList.profilePage] = [roleList.admin, roleList.editor, roleList.normal]
permissionList[accessList.createArticle] = [roleList.admin, roleList.editor, roleList.normal]
permissionList[accessList.updateArticle] = [roleList.admin, roleList.editor, roleList.entityCreator]
permissionList[accessList.deleteArticle] = [roleList.admin, roleList.editor, roleList.entityCreator]
permissionList[accessList.undeleteArticle] = [roleList.admin, roleList.editor, roleList.entityCreator]
permissionList[accessList.banArticle] = [roleList.admin, roleList.editor]
permissionList[accessList.unbanArticle] = [roleList.admin, roleList.editor]
permissionList[accessList.createComment] = [roleList.admin, roleList.editor, roleList.normal]
permissionList[accessList.updateComment] = [roleList.admin, roleList.editor, roleList.entityCreator]
permissionList[accessList.deleteComment] = [roleList.admin, roleList.editor, roleList.entityCreator]
permissionList[accessList.undeleteComment] = [roleList.admin, roleList.editor, roleList.entityCreator]
permissionList[accessList.banComment] = [roleList.admin, roleList.editor]
permissionList[accessList.unbanComment] = [roleList.admin, roleList.editor]
permissionList[accessList.updateProfile] = [roleList.admin, roleList.profileOwner]
var getRoles = function(access, resource, isAuthenticated, entity, user) {
var roles = [roleList.visitor]
if (isAuthenticated) {
roles = [roleList.normal]
if (user.username === 'admin')
roles = [roleList.admin]
else if (user.type === 'editor')
roles = [roleList.editor]
if (resource) {
if (resource === resourceList.profile) {
//Note: on server _id is a object, client _id is string, which does not have equals method
if (entity && entity._id.toString() === user._id.toString())
roles.push(roleList.profileOwner)
}
else if (resource === resourceList.article) {
if (entity && entity.statusMeta.createdBy._id.toString() === user._id.toString())
roles.push(roleList.entityCreator)
}
else if (resource === resourceList.comment) {
if (entity && entity.statusMeta.createdBy._id.toString() === user._id.toString())
roles.push(roleList.entityCreator)
}
}
}
return roles
}
exports.havePermission = function(access, resource, isAuthenticated, entity, user) {
var roles = getRoles(access, resource, isAuthenticated, entity, user)
//Note: we can implement black list here as well, like IP Ban
if (!permissionList[access])
return true
for (var i = 0; i < roles.length; i++) {
var role = roles[i]
if (permissionList[access].indexOf(role) !== -1)
return true
}
return false
}
Then on app/server/helper.js (act as adapter)
var both = require(dir.both + '/both.js')
exports.accessList = both.accessList
exports.resourceList = both.resourceList
exports.havePermission = function(access, resource, req) {
return both.havePermission(access, resource, req.isAuthenticated(), req[resource], req.user)
}
//todo: use this function in other places
exports.getPermissionError = function(message) {
var err = new Error(message || 'you do not have the permission')
err.status = 403
return err
}
exports.getAuthenticationError = function(message) {
var err = new Error(message || 'please sign in')
err.status = 401
return err
}
exports.requiresPermission = function(access, resource) {
return function(req, res, next) {
if (exports.havePermission(access, resource, req))
return next()
else {
if (!req.isAuthenticated())
return next(exports.getAuthenticationError())
else
return next(exports.getPermissionError())
}
}
}
on app/client/helper.js, also act as adapter.
exports.accessList = both.accessList
exports.resourceList = both.resourceList
exports.havePermission = function(access, resource, userService, entity) {
//Note: In debugging, we can grant client helper all access, and test robustness of server
return both.havePermission(access, resource, userService.isAuthenticated(), entity, userService.user)
}
I personnally took inspiration from ghost. In my config there is the perms, and permissions.jsexport a canThisfunction that take the current logged user. Here is the whole project
Part of my config file
"user_groups": {
"admin": {
"full_name": "Administrators",
"description": "Adminsitators.",
"allowedActions": "all"
},
"modo": {
"full_name": "Moderators",
"description": "Moderators.",
"allowedActions": ["mod:*", "comment:*", "user:delete browse add banish edit"]
},
"user": {
"full_name": "User",
"description": "User.",
"allowedActions": ["mod:browse add star", "comment:browse add", "user:browse"]
},
"guest": {
"full_name": "Guest",
"description": "Guest.",
"allowedActions": ["mod:browse", "comment:browse", "user:browse add"]
}
},
mongoose = require("mongoose")
###
This utility function determine whether an user can do this or this
using the permissions. e. g. "mod" "delete"
#param userId the id of the user
#param object the current object name ("mod", "user"...)
#param action to be executed on the object (delete, edit, browse...)
#param owner the optional owner id of the object to be "actionned"
###
# **Important this is a promise but to make a lighter code I removed it**
exports.canThis = (userId, object, action, ownerId, callback) ->
User = mongoose.model("User")
if typeof ownerId is "function"
callback = ownerId
ownerId = undefined
if userId is ""
return process(undefined, object, action, ownerId, callback)
User.findById(userId, (err, user) ->
if err then return callback err
process(user, object, action, ownerId, callback)
)
process = (user, object, action, ownerId, callback) ->
if user then role = user.role or "user"
group = config.user_groups[role or "guest"]
if not group then return callback(new Error "No suitable group")
# Parses the perms
actions = group.allowedActions
for objAction in actions when objAction.indexOf object is 0
# We get all the allowed actions for the object and group
act = objAction.split(":")[1]
obj = objAction.split(":")[0]
if act.split(" ").indexOf(action) isnt -1 and obj is object
return callback true
callback false
config = require "../config"
Usage example:
exports.edit = (userid, name) ->
# Q promise
deferred = Q.defer()
# default value
can = false
# We check wheteher it can or not
canThis(userid, "user", "edit").then((can)->
if not userid
return deferred.reject(error.throwError "", "UNAUTHORIZED")
User = mongoose.model "User"
User.findOne({username: name}).select("username location website public_email company bio").exec()
).then((user) ->
# Can the current user do that?
if not user._id.equals(userid) and can is false
return deferred.reject(error.throwError "", "UNAUTHORIZED")
# Done!
deferred.resolve user
).fail((err) ->
deferred.reject err
)
deferred.promise
Perhaps what I've done isn't good, but it works well as far as I can see.
Check the Node module permission for that matter. It's pretty simple concept, I hope they'll allow all CRUD methods too.
Yes, you can access that through the request argument.
app.use(function(req,res,next){
console.log(req.method);
});
http://nodejs.org/api/http.html#http_message_method
Edit:
Misread your question. It would probably just be better to assign user permissions and allow access to the database based upon the permissions. I don't understand what you mean by validate by means of interaction with the database. If you are already allowing them to interact with the database and they don't have the proper permissions to do so, isn't that a security issue?

Resources