I got the 'createdBy' field added to the model but it allows the admin to select all users as the 'createdBy' user. I want this field to be auto populated with the admin that is currently logged in and can't seem to get it to work.
Ideally this wouldn't appear in the UI at all but just be stored when the user is saved.
User.add({
name: { type: Types.Name, required: true, index: true },
email: { type: Types.Email, initial: true, required: true, index: true },
company: { type: String, required: true, index: true, initial: true },
phone: { type: String, required: true, index: true, initial: true },
password: { type: Types.Password, initial: true, required: true },
createdBy: { type: Types.Relationship, initial:true, required:true, ref: 'User' },
createdAt: { type: Date, default: Date.now }
}, 'Permissions', {
level : { type: Types.Select, numeric: true, options: [{ value: 1, label: 'User' }, { value: 2, label: 'Group Administrator' }, { value: 3, label: 'System Administrator' }] }
},
'Screening', {
rooms : { type: Types.Select, numeric: true, options: [{ value: 1, label: 'Screening room 1' }, { value: 2, label: 'Screening room 2' }, { value: 3, label: 'Screening room 3' }] }
});
While your implementation is functional, a number of Keystone developers (myself included) have raised concerns regarding the security risk of sending the user.id via a POST. You are also correct when you say that currently there is no good way of doing this in Keystone.
My solution was to implement the feature on Keystone itself. I added an optional meta pattern, which I called audit meta. This adds two fields to the List (createdBy and updatedBy) which I populate in the UpdateHandler() using an existing cached copy of req.user. This way there's no need to send user._id via POST.
To use it you just add List.addPattern('audit meta'); after defining your list, just like you would if you were using the standard meta. My implementation of audit meta also adds the standard meta fields, so there's no need to use both.
To implement this I made the following changes to Keystone
First, in lib\list.js I added the following code (prefixed with +) to the addPatern() method:
List.prototype.addPattern = function(pattern) {
switch (pattern) {
...
+ case 'audit meta':
+ var userModel = keystone.get('user model');
+
+ if(!this.schema.path('createdOn') && !this.schema.path('updatedOn')) {
+ this.addPattern('standard meta');
+ }
+
+ if (userModel) {
+ this.add({
+ createdBy: { type: Field.Types.Relationship, ref: userModel, hidden: true, index: true },
+ updatedBy: { type: Field.Types.Relationship, ref: userModel, hidden: true, index: true }
+ });
+ this.map('createdBy', 'createdBy');
+ this.map('modifiedBy', 'updatedBy');
+ }
+ break;
+
}
return this;
Then in lib/updateHandler.js I added the following code to UpdateHandler.prototype.process(), just before progress() is called at then end of the method.
+ // check for audit meta fields (mapped to createdBy/modifiedBy)
+ if (this.list.mappings.createdBy && this.item.isNew) {
+ this.item.set(this.list.mappings.createdBy, this.user._id);
+ }
+ if (this.list.mappings.modifiedBy) {
+ this.item.set(this.list.mappings.modifiedBy, this.user._id);
+ }
Earlier I submitted a pull request (https://github.com/JedWatson/keystone/pull/490) to Keystone, which includes a detailed explanation of my implementation. So, if you need this urgently, you can always fork a copy of Keystone and merge my PR.
Apparently there is no good way to do this but I did come up with a work around using someone else's idea by creating a custom hidden input field. It's not ideal but will work for this project. The default value on createdBy is just so I can make it a required field but it is populated in the form jade template not he initial jade template for that input type.
User.add({
name: { type: Types.Name, required: true, index: true },
email: { type: Types.Email, initial: true, required: true, index: true },
company: { type: String, required: true, index: true, initial: true },
phone: { type: String, required: true, index: true, initial: true },
password: { type: Types.Password, initial: true, required: true },
createdBy: { type: Types.Admin, required: true, initial: true, default: 'createdBy' },
createdAt: { type: Types.Hidden, default: Date.now }
}, 'Permissions', {
level : { type: Types.Select, numeric: true, options: [{ value: 1, label: 'User' }, { value: 2, label: 'Group Administrator' }, { value: 3, label: 'System Administrator' }] }
},Screening', {
rooms : { type: Types.Select, numeric: true, options: [{ value: 1, label: 'Screening room 1' }, { value: 2, label: 'Screening room 2' }, { value: 3, label: 'Screening room 3' }] }
});
then the custom fieldtype just something like this, just create one for form and initial. Also create the fieldTypes/admin.js and update the fieldTypes.index.js
Input admin/form.jade
.field(class='type-' + field.type, data-field-type=field.type, data-field-path=field.path, data-field-collapse=field.collapse ? 'true' : false, data-field-depends-on=field.dependsOn, data-field-noedit=field.noedit ? 'true' : 'false')
- var value = field.format(item)
.field-ui(class='width-' + field.width)
if field.noedit
.field-value= user._id
else
input(type='hidden', name=field.path, value=user._id, autocomplete='off').form-control
if field.note
.field-note!= field.note