const mongoose = require("mongoose");
const expirationTime=10
let currentTime = Date.now();
const userSchema = new mongoose.Schema(
{
fullName: {
type: String,
required: false,
},
email: {
type: String,
required: false,
},
mobile_number: {
type: String,
required: false,
},
password: {
type: String,
required: false,
},
otp_instance: [{
otp_id: {
type: mongoose.Schema.Types.ObjectId,
required: false
},
otp: {
type: String,
// index: {expires:'1m'},
required: false
},
createdAt: {
type: Date,
default: Date()
},
expiration_time: {
type: Date,
default: new Date(currentTime+(expirationTime * 1000))
},
// otp: { type: String, index: { expires: 300 }}
}],
resetLink: {
type: String,
required: false,
},
isAccountVerified: {
type: String,
required: false,
},
token: {
type: String,
required: false,
},
activeStatus: {
type: String,
required: false,
default: "0",
},
deletedStatus: {
type: String,
required: false,
default: "0",
},
},
{ timestamps: true }
);
// userSchema.createIndex( { otp: 1 }, { expireAfterSeconds: 36000 } )
const userModel = mongoose.model("user", userSchema);
module.exports = userModel;
is it possible to expire in 1 min in model of particular data like stored otp in mongodb
is it possible to expire in 1 min in model of particular data like stored otp in mongodb
is it possible to expire in 1 min in model of particular data like stored otp in mongodb> is it possible to expire in 1 min in model of particular data like stored otp in mongodb> is it possible to expire in 1 min in model of particular data like stored otp in mongodb> is it possible to expire in 1 min in model of particular data like stored otp in mongodb> is it possible to expire in 1 min in model of particular data like stored otp in mongodb
I am working on a Nodejs Express API project using mongoDB with mongoose and i would like to get some advice on best practices and going about creating an efficient schema design from community
The app deals with two type of user accounts
Account type :
Single (default)
Organization (can switch to from settings)
Note:
In organisation account there will be a admin (owner) and other invited user and each user is assigned permission level / access level .One user will always be associated with only one account, ie he cannot be invited again to another account or start a new account if he is already part of a existing account. Also billing and shipping address is specific to account rather than user in the case of an organization account (address of user switching to organization account will be the address of Organization account )
I have completed the authentication part with the help of passport.js JWT and local strategy
i tried to develop one similar to RDBMS approach ( i used to be RDBMS guy ) and failed
Models and schemas
const userSchema = new Schema({
first_name: String,
last_name: String,
email: String,
phone: String,
avatar: String,
password: String,
active: Boolean
});
const User = mongoose.model('user', userSchema);
const accountSchema = mongoose.Schema({
account_type: { type: String, enum: ['single', 'organization'], default: 'single' },
organization: { type: Schema.Types.ObjectId, ref: 'organization', required: false },
billing_address: String,
shipping_address: String,
});
const Account = mongoose.model('account', accountSchema);
const accountUserRoleSchema = mongoose.Schema({
user : { type: Schema.Types.ObjectId, ref: 'user', },
role: { type: String, enum: ['admin', 'user'], default: 'user' },
account: { type: Schema.Types.ObjectId, ref: 'account', required: true }
});
const AccountUserRole = mongoose.model('accountUserRole', accountUserRoleSchema);
const permissionSchema = mongoose.Schema({
user : { type: Schema.Types.ObjectId, ref: 'user', required: true },
type: { type: Schema.Types.ObjectId, ref: 'permissionType', required: true },
read: { type: Boolean, default: false, required: true },
write: { type: Boolean, default: false, required: true },
delete: { type: Boolean, default: false, required: true },
accountUser : { type: Schema.Types.ObjectId, ref: 'account',required: true }
});
const Permission = mongoose.model('permission', permissionSchema);
const permissionTypeSchema = mongoose.Schema({
name : { type: String, required: true }
});
const PermissionType = mongoose.model('permissionType', permissionTypeSchema);
const organizationSchema = mongoose.Schema({
account : { type: Schema.Types.ObjectId, ref: 'account', },
name: { type: String, required: true },
logo: { type: String, required: true }
});
const Organization = mongoose.model('organization', organizationSchema);
Now i am developing Authorisation part where the user need to be restricted access to the resource by checking the permission he or she is assigned with .
The solution i found was to develop a Authorisation middleware which run after the authentication middleware which check for the access permissions assigned
But the problem appeared while i tried to access account data based on the user currently logged in , as i will have to search document based on the objectId reference . And i could understand that this could happen if i continue with my current design .This works fine but searching document using objectId reference seems not be a good idea
Authorization middleware
module.exports = {
checkAccess : (permission_type,action) => {
return async (req, res, next) => {
// check if the user object is in the request after verifying jwt
if(req.user){
// find the accountUserRole with the user data from the req after passort jwt auth
const accountUser = await AccountUserRole.findOne({ user :new ObjectId( req.user._id) }).populate('account');
if(accountUser)
{
// find the account and check the type
if(accountUser.account)
{
if(accountUser.account.type === 'single')
{
// if account is single grant access
return next();
}
else if(accountUser.account.type === 'organization'){
// find the user permission
// check permission with permission type and see if action is true
// if true move to next middileware else throw access denied error
}
}
}
}
}
}
}
I decided to scrap my current schema as i understand that forcing RDBMS approach on NoSQL is a bad idea.
Unlike relational databases, with MongoDB the best schema design depends a lot on how you're going to be accessing the data. What will you be using the Account data for, and how will you be accessing it
My new redesigned schema and models
const userSchema = new Schema({
first_name: String,
last_name: String,
email: String,
phone: String,
avatar: String,
password: String,
active: Boolean
account : { type: Schema.Types.ObjectId, ref: 'account', },
role: { type: String, enum: ['admin', 'user'], default: 'user' },
permssion: [
{
type: { type: Schema.Types.ObjectId, ref: 'permissionType', required: true },
read: { type: Boolean, default: false, required: true },
write: { type: Boolean, default: false, required: true },
delete: { type: Boolean, default: false, required: true },
}
]
});
const User = mongoose.model('user', userSchema);
const accountSchema = mongoose.Schema({
account_type: { type: String, enum: ['single', 'organization'], default: 'single' },
organization: {
name: { type: String, required: true },
logo: { type: String, required: true }
},
billing_address: String,
shipping_address: String,
});
const Account = mongoose.model('account', accountSchema);
const permissionTypeSchema = mongoose.Schema({
name : { type: String, required: true }
});
const PermissionType = mongoose.model('permissionType', permissionTypeSchema);
Still i am not sure if this is the right way to do it , please help me with you suggestions.
you can merge user and user account schema :
added some more fileds which is useful to you .
const userSchema = new Schema({
first_name: { type: String,default:'',required:true},
last_name: { type: String,default:'',required:true},
email: { type: String,unique:true,required:true,index: true},
email_verified :{type: Boolean,default:false},
email_verify_token:{type: String,default:null},
phone: { type: String,default:''},
phone_verified :{type: Boolean,default:false},
phone_otp_number:{type:Number,default:null},
phone_otp_expired_at:{ type: Date,default:null},
avatar: { type: String,default:''},
password: { type: String,required:true},
password_reset_token:{type: String,default:null},
reset_token_expired_at: { type: Date,default:null},
active: { type: Boolean,default:true}
account_type: { type: String, enum: ['single', 'organization'], default: 'single' },
organization: {type:Schema.Types.Mixed,default:{}},
billing_address: { type: String,default:''}
shipping_address: { type: String,default:''}
role: { type: String, enum: ['admin', 'user'], default: 'user' },
permission: [
{
type: { type: Schema.Types.ObjectId, ref: 'permissionType', required: true },
read: { type: Boolean, default: false, required: true },
write: { type: Boolean, default: false, required: true },
delete: { type: Boolean, default: false, required: true },
}
],
created_at: { type: Date, default: Date.now },
updated_at: { type: Date, default: Date.now }
});
in your middleware :
module.exports = {
checkAccess : (permission_type,action) => {
return async (req, res, next) => {
// check if the user object is in the request after verifying jwt
if(req.user){
if(req.user.account_type === 'single')
{
// if account is single grant access
return next();
}
else{
// find the user permission
// check permission with permission type and see if action is true
// if true move to next middileware else throw access denied error
}
}
}
}
};
I would suggest:
1 - Define your permission levels, for example: If the user is assigned to a specific Role / Permission level, what features/options he can access.
2 - Permission levels should be recognized by Number (1 = Admin, 2 = User) etc and that key should be indexed in MongoDB (You can use and rely on the ObjectID as well).
3 - Your user object/schema should only have a permission key with the type of Number in Mongoose - no need to create a separate schema for this.
const userSchema = new Schema({
first_name: String,
last_name: String,
email: String,
phone: String,
avatar: String,
password: String,
active: Boolean
account : { type: Schema.Types.ObjectId, ref: 'account', },
permssion: {type: Number, required: true, default: 2} // Default's User
});
With this approach, you can modify your auth check middleware to just check if the permission level sent by the client is identified by the DB and if it does, give the user access else throw access denied error.
If you want you can add another field with permission type and return the name of the permission as well but I think you should handle it on the client, not on the server / be.
I partially understood the requirements (Bad at reading too many words) so I have left anything untouched, let me know.
I have created this schema for user registration:
let userSchema = new mongoose.Schema({
lname: String,
fname: String,
username: String,
email: String,
password: String,
registrationDate: {
type: Date,
default: Date.now()
},
referedBy: {
type: String,
default: ''
},
referalEnd: {
type: Date,
default: Date.now() + 5*365*24*60*60*1000
},
userRefererId: {
type: String,
default: uniqid()
}
});
As you can see, there is a Date.now function and uniqid function in the schema.
Those functions can be used approximately once every 5 minutes,
because if I create two users a few seconds apart, it generates the same uniqid and shows the same date.
Remove the () from Date.now() and just call Date.now.
I've run into this before, the schema is generated at deployment / start time and not regenerated on each new creation hence why the time is always the same. Its better to generate the date / time outside the new Model().save() call.
let userSchema = new mongoose.Schema({
lname: String,
fname:String,
username: String,
email: String,
password: String,
registrationDate: {
type: Date,
default: function(){return Date.now()}
},
referedBy: {
type:String,
default: ''
},
referalEnd: {
type: Date,
default: function(){ return Date.now() + 5*365*24*60*60*1000}
},
userRefererId: {
type:String,
default: uniqid()
}
});
I know, if i want to set the expire time for one collection with the expires property like so:
new Schema({
token: {
type: String,
required: true
},
createdAt: {
type: Date,
expires: '10s',
default: Date.now
}
});
But, how can i set the expire time for one property in a collection?
For example i have an schema with username, email and a array of authentification tokens. I want every token to be deleted from the creation date after 10s. I tried:
new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
tokens: [{
createdAt: {
type: Date,
expires: '10s',
default: Date.now
},
auth: {
type: String,
required: true
},
token: {
type: String,
required: true
}
}]
});
But every time the complete collection will be deleted, not the token object.
You can create a separate collection for holding authentication tokens, and maintain a one-to-many relationship between user and token collections.
you can cascade insert/update/delete using pre/post middlewares, so when token expires de-reference token in user
user schema
const UserSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
tokens: [
{ type: Schema.Types.ObjectId, ref: 'Token' }
]
});
token schema (make sure you have created ttl index on createdAt)
const TokenSchma = new Schema({
user : {
type: Schema.Types.ObjectId, ref: 'User'
},
createdAt: {
type: Date,
expires: '10s',
default: Date.now
},
auth: {
type: String,
required: true
},
token: {
type: String,
required: true
}
}
);
Im using MongoDb, and I have a workspace schema with mongoose (v4.0.1):
var Workspace = new mongoose.Schema({
name: {
type: String,
required: true
},
userId: {
type: String,
required: true
},
createdOn: {
type: Date,
"default": Date.now
}
});
And a user schema:
var User = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
organisation: {
type: String,
required: true
},
location: {
type: String,
required: true
},
verifyString: {
type: String
},
verified: {
type: Boolean,
default: false
},
password: {
type: String,
required: true
},
createdOn: {
type: Date,
"default": Date.now
},
isAdmin: {
type: Boolean,
default: false
}
});
So the Workspace userId is the ObjectID from the User document.
When Im logged in as an adminstrator, I want to get all workspaces, as well as the email of the user that owns the workspace.
What Im doing is getting very messy:
Workspace.find({}).exec.then(function(workspaceObects){
var userPromise = workspaceObects.map(function(workspaceObect){
// get the user model with workspaceObect.userId here
});
// somehow combine workspaceObjects and users
});
The above doesnt work and gets extremely messy. Basically I have to loop through the workspaceObjects and go retrieve the user object from the workspace userId. But because its all promises and it becomes very complex and easy to make a mistake.
Is there a much simpler way to do this? In SQL it would require one simple join. Is my schema wrong? Can I get all workspaces and their user owners email in one Mongoose query?
var Workspace = new mongoose.Schema({
userId: {
type: String,
required: true,
ref: 'User' //add this to your schema
}
});
Workspace.find().populate('userId').exec( (err, res) => {
//you will have res with all user fields
});
http://mongoosejs.com/docs/populate.html
Mongo don't have joins but mongoose provides a very powerfull tool to help you with you have to change the model a little bit and use populate:
Mongoose population
You have to make a few changes to your models and get the info of the user model inside your workspace model.
Hope it helps